def test_get_pullspecs_some_locations(self, rel_images, rel_envs, containers,
                                          annotations, init_rel_envs, init_containers):
        data = ORIGINAL.data
        expected = {p.value for p in PULLSPECS.values()}

        if not rel_images:
            expected -= {RI1.value, RI2.value}
            del data["spec"]["relatedImages"]
        deployments = chain_get(data, ["spec", "install", "spec", "deployments"])
        if not rel_envs:
            expected -= {CE1.value}
            for d in deployments:
                for c in chain_get(d, ["spec", "template", "spec", "containers"]):
                    c.pop("env", None)
        if not containers:
            expected -= {C1.value, C2.value, C3.value}
            for d in deployments:
                del d["spec"]["template"]["spec"]["containers"]
        if not annotations:
            expected -= {AN1.value, AN2.value, AN3.value,
                         AN4.value, AN5.value, AN6.value, AN7.value}
            delete_all_annotations(data)
        if not init_rel_envs:
            expected -= {ICE1.value}
            for d in deployments:
                for c in chain_get(d, ["spec", "template", "spec", "initContainers"], default=[]):
                    c.pop("env", None)
        if not init_containers:
            expected -= {IC1.value}
            for d in deployments:
                d["spec"]["template"]["spec"].pop("initContainers", None)
        self._mock_check_csv()
        csv = OperatorCSV("x.yaml", data)
        assert csv.get_pullspecs() == expected
    def test_get_pullspecs(self, caplog):
        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        pullspecs = csv.get_pullspecs()
        assert pullspecs == self._original_pullspecs

        expected_logs = [
            "original.yaml - Found pullspec for relatedImage ri1: {ri1}",
            "original.yaml - Found pullspec for relatedImage ri2: {ri2}",
            "original.yaml - Found pullspec for RELATED_IMAGE_CE1 var: {ce1}",
            "original.yaml - Found pullspec for RELATED_IMAGE_ICE1 var: {ice1}",
            "original.yaml - Found pullspec for container c1: {c1}",
            "original.yaml - Found pullspec for container c2: {c2}",
            "original.yaml - Found pullspec for container c3: {c3}",
            "original.yaml - Found pullspec for initContainer ic1: {ic1}",
            "original.yaml - Found pullspec for {an1.key} annotation: {an1}",
            "original.yaml - Found pullspec for {an2.key} annotation: {an2}",
            "original.yaml - Found pullspec for {an2.key} annotation: {an2}",
            "original.yaml - Found pullspec for {an3.key} annotation: {an3}",
            "original.yaml - Found pullspec for {an4.key} annotation: {an4}",
            "original.yaml - Found pullspec for {an5.key} annotation: {an5}",
            "original.yaml - Found pullspec for {an6.key} annotation: {an6}",
            "original.yaml - Found pullspec for {an7.key} annotation: {an7}",
        ]
        for log in expected_logs:
            assert log.format(**PULLSPECS) in caplog.text
 def test_ignored_annotations(self):
     data = {
         'kind': 'ClusterServiceVersion',
         'metadata': {
             'annotations': {
                 'some_text': 'abcdef',
                 'some_number': 123,
                 'some_array': [],
                 'some_object': {},
                 'metadata': {
                     'annotations': {
                         'pullspec': 'metadata.annotations/nested.in:metadata.annotations'
                     }
                 }
             },
             'not_an_annotation': 'something.that/looks-like:a-pullspec',
             'not_annotations': {
                 'also_not_an_annotation': 'other.pullspec/lookalike:thing'
             },
             'metadata': {
                 'annotations': {
                     'pullspec': 'metadata.annotations/nested.in:metadata'
                 }
             }
         }
     }
     self._mock_check_csv()
     csv = OperatorCSV("original.yaml", data)
     assert csv.get_pullspecs() == set()
    def test_known_vs_other_annotations(self):
        # All annotation must be found and replaced exactly once, heuristic
        # must not look in keys that are known pullspec sources
        data = {
            'kind': 'ClusterServiceVersion',
            'metadata': {
                'annotations': {
                    'containerImage': 'a.b/c:1',
                    'notContainerImage': 'a.b/c:1'
                }
            },
            'spec': {
                'metadata': {
                    'annotations': {
                        'containerImage': 'a.b/c:1',
                        'notContainerImage': 'a.b/c:1'
                    }
                }
            }
        }
        replacements = {
            ImageName.parse(old): ImageName.parse(new) for old, new in [
                ('a.b/c:1', 'd.e/f:1'),
                ('d.e/f:1', 'g.h/i:1'),
            ]
        }
        self._mock_check_csv()
        csv = OperatorCSV("original.yaml", data)
        csv.replace_pullspecs(replacements)

        assert csv.data["metadata"]["annotations"]["containerImage"] == 'd.e/f:1'
        assert csv.data["metadata"]["annotations"]["notContainerImage"] == 'd.e/f:1'
        assert csv.data["spec"]["metadata"]["annotations"]["containerImage"] == 'd.e/f:1'
        assert csv.data["spec"]["metadata"]["annotations"]["notContainerImage"] == 'd.e/f:1'
 def test_has_related_image_envs(self, var, does_have):
     data = {
         'kind': 'ClusterServiceVersion',
         'spec': {
             'install': {
                 'spec': {
                     'deployments': [
                         {
                             'spec': {
                                 'template': {
                                     'spec': {
                                         'containers': [
                                             {'name': 'spam', 'image': 'eggs', 'env': []}
                                         ]
                                     }
                                 }
                             }
                         }
                     ]
                 }
             }
         }
     }
     if var is not None:
         deployment = data['spec']['install']['spec']['deployments'][0]
         deployment['spec']['template']['spec']['containers'][0]['env'].append(var)
     self._mock_check_csv()
     csv = OperatorCSV('original.yaml', data)
     assert csv.has_related_image_envs() == does_have
    def test_replace_pullspecs_everywhere(self, caplog):
        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        csv.replace_pullspecs_everywhere(self._replacement_pullspecs)
        assert csv.data == REPLACED_EVERYWHERE.data

        expected_logs = {
            "{file} - Replaced pullspec: {ri1} -> {ri1.replace}": 3,
            "{file} - Replaced pullspec: {ri2} -> {ri2.replace}": 3,
            "{file} - Replaced pullspec: {ce1} -> {ce1.replace}": 4,
            "{file} - Replaced pullspec: {c1} -> {c1.replace}": 3,
            "{file} - Replaced pullspec: {c2} -> {c2.replace}": 3,
            "{file} - Replaced pullspec: {c3} -> {c3.replace}": 3,
            "{file} - Replaced pullspec: {an1} -> {an1.replace}": 2,
            "{file} - Replaced pullspec: {ic1} -> {ic1.replace}": 3,
            "{file} - Replaced pullspec: {ice1} -> {ice1.replace}": 3,
            "{file} - Replaced pullspec for {an1.key} annotation: {an1} -> {an1.replace}": 1,
            "{file} - Replaced pullspec for {an2.key} annotation: {an2} -> {an2.replace}": 1,
            "{file} - Replaced pullspec for {an3.key} annotation: {an3} -> {an3.replace}": 1,
            "{file} - Replaced pullspec for {an4.key} annotation: {an4} -> {an4.replace}": 1,
            "{file} - Replaced pullspec for {an5.key} annotation: {an5} -> {an5.replace}": 1,
            "{file} - Replaced pullspec for {an6.key} annotation: {an6} -> {an6.replace}": 2,
            "{file} - Replaced pullspec for {an7.key} annotation: {an7} -> {an7.replace}": 2,
        }
        # NOTE: an1 gets replaced once as an annotation and twice in other places
        # an6 and an7 both get replaced twice in the same annotation

        for log, count in expected_logs.items():
            assert caplog.text.count(log.format(file="original.yaml", **PULLSPECS)) == count
Example #7
0
    def test_get_pullspecs_some_locations(self, rel_images, rel_envs,
                                          containers, annotations):
        data = ORIGINAL.data
        expected = {p.value for p in PULLSPECS.values()}

        if not rel_images:
            expected -= {FOO.value, BAR.value}
            del data["spec"]["relatedImages"]
        deployments = chain_get(data,
                                ["spec", "install", "spec", "deployments"])
        if not rel_envs:
            expected -= {EGGS.value}
            for d in deployments:
                for c in chain_get(d,
                                   ["spec", "template", "spec", "containers"]):
                    c.pop("env", None)
        if not containers:
            expected -= {SPAM.value, HAM.value, JAM.value}
            for d in deployments:
                del d["spec"]["template"]["spec"]["containers"]
        if not annotations:
            expected -= {BAZ.value}
            del data["metadata"]["annotations"]

        csv = OperatorCSV("x.yaml", data)
        assert csv.get_pullspecs() == expected
Example #8
0
 def test_has_related_images(self, pullspecs, does_have):
     data = {
         'kind': 'ClusterServiceVersion',
         'spec': {}
     }
     if pullspecs is not None:
         data['spec']['relatedImages'] = pullspecs
     csv = OperatorCSV('original.yaml', data)
     assert csv.has_related_images() == does_have
Example #9
0
    def test_dump(self, tmpdir):
        path = tmpdir.join("original.yaml")
        csv = OperatorCSV(str(path), ORIGINAL.data)
        csv.dump()

        content = path.read()
        # Formatting does not necessarily have to match, at least check the data...
        assert yaml.load(content) == csv.data
        # ...and that the comment was preserved
        assert content.startswith('# A meaningful comment')
    def test_valuefrom_references_not_allowed(self):
        data = ORIGINAL.data
        env_path = CE1.path[:-1]
        env = chain_get(data, env_path)
        env["valueFrom"] = "somewhere"

        csv = OperatorCSV("original.yaml", data)
        with pytest.raises(RuntimeError) as exc_info:
            csv.get_pullspecs()

        assert '"valueFrom" references are not supported' in str(exc_info.value)
    def test_set_related_images(self, caplog):
        data = ORIGINAL.data
        csv = OperatorCSV("original.yaml", data)
        csv.set_related_images()

        # the order is:
        #   1. existing relatedImages
        #   2. known annotations
        #   3. containers
        #   4. initContainers
        #   5. container env vars
        #   6. initContainer env vars
        #   7. other annotations (in reverse order - quirky, I know)
        expected_related_images = [
            CommentedMap([("name", name), ("image", pullspec.value.to_str())])
            for name, pullspec in [
                ("ri1", RI1),
                ("ri2", RI2),
                ("baz-latest-annotation", AN1),
                ("c1", C1),
                ("c2", C2),
                ("c3", C3),
                ("ic1", IC1),
                ("ce1", CE1),
                ("ice1", ICE1),
                ("an7-1-annotation", AN7),
                ("an6-1-annotation", AN6),
                ("an5-1-annotation", AN5),
                ("an4-1-annotation", AN4),
                ("an3-1-annotation", AN3),
                ("an2-1-annotation", AN2),
            ]
        ]
        assert csv.data["spec"]["relatedImages"] == expected_related_images

        expected_logs = [
            "{path} - Set relatedImage ri1 (from relatedImage ri1): {ri1}",
            "{path} - Set relatedImage ri2 (from relatedImage ri2): {ri2}",
            "{path} - Set relatedImage baz-latest-annotation (from {an1.key} annotation): {an1}",
            "{path} - Set relatedImage c1 (from container c1): {c1}",
            "{path} - Set relatedImage c2 (from container c2): {c2}",
            "{path} - Set relatedImage c3 (from container c3): {c3}",
            "{path} - Set relatedImage ic1 (from initContainer ic1): {ic1}",
            "{path} - Set relatedImage ce1 (from RELATED_IMAGE_CE1 var): {ce1}",
            "{path} - Set relatedImage ice1 (from RELATED_IMAGE_ICE1 var): {ice1}",
            "{path} - Set relatedImage an2-1-annotation (from {an2.key} annotation): {an2}",
            "{path} - Set relatedImage an3-1-annotation (from {an3.key} annotation): {an3}",
            "{path} - Set relatedImage an4-1-annotation (from {an4.key} annotation): {an4}",
            "{path} - Set relatedImage an5-1-annotation (from {an5.key} annotation): {an5}",
            "{path} - Set relatedImage an6-1-annotation (from {an6.key} annotation): {an6}",
            "{path} - Set relatedImage an7-1-annotation (from {an7.key} annotation): {an7}",
        ]
        for log in expected_logs:
            assert log.format(path="original.yaml", **PULLSPECS) in caplog.text
Example #12
0
    def test_wrong_kind(self):
        data = ORIGINAL.data

        del data["kind"]
        with pytest.raises(NotOperatorCSV) as exc_info:
            OperatorCSV("original.yaml", data)
        assert str(exc_info.value) == "Not a ClusterServiceVersion"

        data["kind"] = "ClusterResourceDefinition"
        with pytest.raises(NotOperatorCSV) as exc_info:
            OperatorCSV("original.yaml", data)
        assert str(exc_info.value) == "Not a ClusterServiceVersion"
 def test_related_annotation_names(self, pullspec, name):
     data = {
         'kind': 'ClusterServiceVersion',
         'metadata': {
             'annotations': {
                 'foo': pullspec
             }
         }
     }
     self._mock_check_csv()
     csv = OperatorCSV("original.yaml", data)
     csv.set_related_images()
     generated_name = csv.data["spec"]["relatedImages"][0]["name"]
     assert generated_name == name
 def test_related_images_no_pullspecs(self, caplog):
     """If no pullspecs are detected, skip creation of relatedImages"""
     data = {
         'kind': 'ClusterServiceVersion',
         'metadata': {
             'annotations': {
                 'foo': 'test'
             }
         }
     }
     self._mock_check_csv()
     csv = OperatorCSV("original.yaml", data)
     csv.set_related_images()
     assert "" in caplog.text
     assert 'relatedImages' not in csv.data.get("spec", {})
Example #15
0
    def test_from_file(self, tmpdir):
        path = tmpdir.join("original.yaml")
        path.write(ORIGINAL.content)

        csv = OperatorCSV.from_file(str(path))
        assert csv.path == str(path)
        assert csv.data == ORIGINAL.data
Example #16
0
    def test_replace_pullspecs(self, caplog):
        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        csv.replace_pullspecs(self._replacement_pullspecs)
        assert csv.data == REPLACED.data

        expected_logs = [
            "{file} - Replaced pullspec for related image foo: {foo} -> {foo.replace}",
            "{file} - Replaced pullspec for related image bar: {bar} -> {bar.replace}",
            "{file} - Replaced pullspec in RELATED_IMAGE_EGGS var: {eggs} -> {eggs.replace}",
            "{file} - Replaced pullspec for container spam: {spam} -> {spam.replace}",
            "{file} - Replaced pullspec for container ham: {ham} -> {ham.replace}",
            "{file} - Replaced pullspec for container jam: {jam} -> {jam.replace}",
            "{file} - Replaced pullspec in annotations: {baz} -> {baz.replace}",
        ]
        for log in expected_logs:
            assert log.format(file="original.yaml", **PULLSPECS) in caplog.text
Example #17
0
    def test_replace_pullspecs_everywhere(self, caplog):
        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        csv.replace_pullspecs_everywhere(self._replacement_pullspecs)
        assert csv.data == REPLACED_EVERYWHERE.data

        expected_logs = {
            "original.yaml - Replaced pullspec: {foo} -> {foo.replace}": 3,
            "original.yaml - Replaced pullspec: {bar} -> {bar.replace}": 3,
            "original.yaml - Replaced pullspec: {eggs} -> {eggs.replace}": 4,
            "original.yaml - Replaced pullspec: {spam} -> {spam.replace}": 3,
            "original.yaml - Replaced pullspec: {ham} -> {ham.replace}": 3,
            "original.yaml - Replaced pullspec: {jam} -> {jam.replace}": 3,
            "original.yaml - Replaced pullspec: {baz} -> {baz.replace}": 3,
        }
        for log, count in expected_logs.items():
            assert caplog.text.count(log.format(**PULLSPECS)) == count
Example #18
0
    def test_get_pullspecs(self, caplog):
        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        pullspecs = csv.get_pullspecs()
        assert pullspecs == self._original_pullspecs

        expected_logs = [
            "original.yaml - Found pullspec for related image foo: {foo}",
            "original.yaml - Found pullspec for related image bar: {bar}",
            "original.yaml - Found pullspec in RELATED_IMAGE_EGGS var: {eggs}",
            "original.yaml - Found pullspec for container spam: {spam}",
            "original.yaml - Found pullspec for container ham: {ham}",
            "original.yaml - Found pullspec for container jam: {jam}",
            "original.yaml - Found pullspec in annotations: {baz}",
        ]
        for log in expected_logs:
            assert log.format(**PULLSPECS) in caplog.text
 def test_tricky_annotation_replacements(self, pullspecs, replacements, expected):
     replacements = {
         ImageName.parse(old): ImageName.parse(new)
         for old, new in replacements.items()
     }
     data = {
         'kind': 'ClusterServiceVersion',
         'metadata': {
             'annotations': {
                 'foo': ", ".join(pullspecs)
             }
         }
     }
     self._mock_check_csv()
     csv = OperatorCSV("original.yaml", data)
     csv.replace_pullspecs(replacements)
     assert csv.data['metadata']['annotations']['foo'] == ", ".join(expected)
    def test_replace_only_some_pullspecs(self, caplog):
        replacement_pullspecs = self._replacement_pullspecs.copy()

        # ri1 won't be replaced because replacement is identical
        replacement_pullspecs[RI1.value] = RI1.value
        # ri2 won't be replaced because no replacement available
        del replacement_pullspecs[RI2.value]

        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        csv.replace_pullspecs(replacement_pullspecs)

        assert RI1.find_in_data(csv.data) == RI1.value
        assert RI2.find_in_data(csv.data) == RI2.value

        ri1_log = "original.yaml - Replaced pullspec for relatedImage ri1: {ri1}"
        ri2_log = "original.yaml - Replaced pullspec for relatedImage ri2: {ri2}"

        assert ri1_log.format(ri1=RI1) not in caplog.text
        assert ri2_log.format(ri2=RI2) not in caplog.text
Example #21
0
    def test_replace_only_some_pullspecs(self, caplog):
        replacement_pullspecs = self._replacement_pullspecs.copy()

        # Foo won't be replaced because replacement is identical
        replacement_pullspecs[FOO.value] = FOO.value
        # Bar won't be replaced because no replacement available
        del replacement_pullspecs[BAR.value]

        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        csv.replace_pullspecs(replacement_pullspecs)

        assert FOO.find_in_data(csv.data) == FOO.value
        assert BAR.find_in_data(csv.data) == BAR.value

        foo_log = "original.yaml - Replaced pullspec for related image foo: {foo}"
        bar_log = "original.yaml - Replaced pullspec for related image bar: {bar}"

        assert foo_log.format(foo=FOO) not in caplog.text
        assert bar_log.format(bar=BAR) not in caplog.text
Example #22
0
    def test_set_related_images(self, caplog):
        data = ORIGINAL.data
        csv = OperatorCSV("original.yaml", data)
        csv.set_related_images()

        # the order is:
        #   1. existing relatedImages
        #   2. annotations
        #   3. containers
        #   4. initContainers
        #   5. container env vars
        #   6. initContainer env vars
        expected_related_images = [
            CommentedMap([("name", name), ("image", pullspec.value.to_str())])
            for name, pullspec in [
                ("foo", FOO),
                ("bar", BAR),
                ("baz-annotation", BAZ),
                ("spam", SPAM),
                ("ham", HAM),
                ("jam", JAM),
                ("p1", P1),
                ("eggs", EGGS),
                ("p2", P2),
            ]
        ]
        assert csv.data["spec"]["relatedImages"] == expected_related_images

        expected_logs = [
            "{path} - Set relatedImage foo (from relatedImage foo): {foo}",
            "{path} - Set relatedImage bar (from relatedImage bar): {bar}",
            "{path} - Set relatedImage baz-annotation (from containerImage annotation): {baz}",
            "{path} - Set relatedImage spam (from container spam): {spam}",
            "{path} - Set relatedImage ham (from container ham): {ham}",
            "{path} - Set relatedImage jam (from container jam): {jam}",
            "{path} - Set relatedImage p1 (from initContainer p1): {p1}",
            "{path} - Set relatedImage eggs (from RELATED_IMAGE_EGGS var): {eggs}",
            "{path} - Set relatedImage p2 (from RELATED_IMAGE_P2 var): {p2}",
        ]
        for log in expected_logs:
            assert log.format(path="original.yaml", **PULLSPECS) in caplog.text
    def test_set_related_images_conflicts(self, related_images, containers, err_msg):
        data = {
            "kind": "ClusterServiceVersion",
            "spec": {
                "relatedImages": related_images,
                "install": {
                    "spec": {
                        "deployments": [
                            {
                                "spec": {
                                    "template": {
                                        "spec": {
                                            "containers": containers
                                        }
                                    }
                                }
                            }
                        ]
                    }
                }
            }
        }
        self._mock_check_csv()
        csv = OperatorCSV("original.yaml", data)

        if err_msg is not None:
            with pytest.raises(RuntimeError) as exc_info:
                csv.set_related_images()
            assert str(exc_info.value) == err_msg.format(path="original.yaml")
        else:
            csv.set_related_images()
            updated_counts = Counter(x['name'] for x in csv.data['spec']['relatedImages'])
            # check that there are no duplicates in .spec.relatedImages
            for name, count in updated_counts.items():
                assert count == 1, 'Duplicate in relatedImages: {}'.format(name)
    def test_replace_pullspecs(self, caplog):
        csv = OperatorCSV("original.yaml", ORIGINAL.data)
        csv.replace_pullspecs(self._replacement_pullspecs)
        assert csv.data == REPLACED.data

        expected_logs = [
            "{file} - Replaced pullspec for relatedImage ri1: {ri1} -> {ri1.replace}",
            "{file} - Replaced pullspec for relatedImage ri2: {ri2} -> {ri2.replace}",
            "{file} - Replaced pullspec for RELATED_IMAGE_CE1 var: {ce1} -> {ce1.replace}",
            "{file} - Replaced pullspec for RELATED_IMAGE_ICE1 var: {ice1} -> {ice1.replace}",
            "{file} - Replaced pullspec for container c1: {c1} -> {c1.replace}",
            "{file} - Replaced pullspec for container c2: {c2} -> {c2.replace}",
            "{file} - Replaced pullspec for container c3: {c3} -> {c3.replace}",
            "{file} - Replaced pullspec for initContainer ic1: {ic1} -> {ic1.replace}",
            "{file} - Replaced pullspec for {an1.key} annotation: {an1} -> {an1.replace}",
            "{file} - Replaced pullspec for {an2.key} annotation: {an2} -> {an2.replace}",
            "{file} - Replaced pullspec for {an3.key} annotation: {an3} -> {an3.replace}",
            "{file} - Replaced pullspec for {an4.key} annotation: {an4} -> {an4.replace}",
            "{file} - Replaced pullspec for {an5.key} annotation: {an5} -> {an5.replace}",
            "{file} - Replaced pullspec for {an6.key} annotation: {an6} -> {an6.replace}",
            "{file} - Replaced pullspec for {an7.key} annotation: {an7} -> {an7.replace}",
        ]
        for log in expected_logs:
            assert log.format(file="original.yaml", **PULLSPECS) in caplog.text
 def test_related_annotation_name_conflicts(self, p1, p2, should_fail):
     data = {
         'kind': 'ClusterServiceVersion',
         'metadata': {
             'annotations': {
                 'foo': "{}, {}".format(p1, p2)
             }
         }
     }
     self._mock_check_csv()
     csv = OperatorCSV("original.yaml", data)
     if should_fail:
         with pytest.raises(RuntimeError) as exc_info:
             csv.set_related_images()
         msg = ("original.yaml - Found conflicts when setting relatedImages:\n"
                "foo annotation: {} X foo annotation: {}".format(p2, p1))
         assert str(exc_info.value) == msg
     else:
         csv.set_related_images()
 def test_yaml_not_object(self):
     data = YAML_LIST.data
     with pytest.raises(NotOperatorCSV) as exc_info:
         OperatorCSV("original.yaml", data)
     assert str(exc_info.value) == "File does not contain a YAML object"
 def test_get_related_image_pullsepcs(self, csv_content, expected_pullspecs):
     self._mock_check_csv()
     csv = OperatorCSV('csv.yaml', csv_content)
     assert expected_pullspecs == csv.get_related_image_pullspecs()