From 0252027eacfa7c8ebb87160e963dfcae6ede92e8 Mon Sep 17 00:00:00 2001 From: Michał Górny Date: Wed, 1 Nov 2017 15:00:23 +0100 Subject: Support controlling entry types via a profile --- gemato/profile.py | 49 +++++++++++++++++ gemato/recursiveloader.py | 32 ++++++++--- tests/test_profile.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 gemato/profile.py create mode 100644 tests/test_profile.py diff --git a/gemato/profile.py b/gemato/profile.py new file mode 100644 index 0000000..2b551a6 --- /dev/null +++ b/gemato/profile.py @@ -0,0 +1,49 @@ +# gemato: Profile support +# vim:fileencoding=utf-8 +# (c) 2017 Michał Górny +# Licensed under the terms of 2-clause BSD license + +import os.path + + +class DefaultProfile(object): + """ + Profile is a class describing the specific properties of a directory + tree. It is used when updating Manifests to determine the most + correct behavior for a given use case. + """ + + def get_entry_type_for_path(self, path): + """ + Get Manifest entry type appropriate for the specified path. + Must return an appropriate Manifest tag for file-style entry + (i.e. one of DATA, MISC, EBUILD, AUX). + """ + return 'DATA' + + +class EbuildRepositoryProfile(DefaultProfile): + """ + A profile suited for a modern ebuild repository. + """ + pass + + +class BackwardsCompatEbuildRepositoryProfile(EbuildRepositoryProfile): + """ + A profile for ebuild repository that maintains compatibility + with Manifest2 format. + """ + + def get_entry_type_for_path(self, path): + spl = path.split(os.path.sep) + if len(spl) == 3: + if path.endswith('.ebuild'): + return 'EBUILD' + elif spl[2] == 'metadata.xml': + return 'MISC' + if spl[2:3] == ['files']: + return 'AUX' + + return (super(BackwardsCompatEbuildRepositoryProfile, self) + .get_entry_type_for_path(path)) diff --git a/gemato/recursiveloader.py b/gemato/recursiveloader.py index cb6d247..fd926c5 100644 --- a/gemato/recursiveloader.py +++ b/gemato/recursiveloader.py @@ -9,6 +9,7 @@ import os.path import gemato.compression import gemato.exceptions import gemato.manifest +import gemato.profile import gemato.util import gemato.verify @@ -22,12 +23,14 @@ class ManifestRecursiveLoader(object): __slots__ = ['root_directory', 'loaded_manifests', 'verify_openpgp', 'openpgp_env', 'sign_openpgp', 'openpgp_keyid', 'hashes', - 'openpgp_signed', 'updated_manifests', 'manifest_device'] + 'openpgp_signed', 'updated_manifests', 'manifest_device', + 'profile'] def __init__(self, top_manifest_path, verify_openpgp=True, openpgp_env=None, sign_openpgp=None, openpgp_keyid=None, - hashes=None, allow_create=False): + hashes=None, allow_create=False, + profile=gemato.profile.DefaultProfile()): """ Instantiate the loader for a Manifest tree starting at top-level Manifest @top_manifest_path. @@ -53,6 +56,8 @@ class ManifestRecursiveLoader(object): If @allow_create is True and @top_manifest_path does not exist, a new Manifest tree will be initialized. Otherwise, opening a non-existing file will cause an exception. + + @profile can be used to provide the profile for the repository. """ self.root_directory = os.path.dirname(top_manifest_path) @@ -61,6 +66,7 @@ class ManifestRecursiveLoader(object): self.sign_openpgp = sign_openpgp self.openpgp_keyid = openpgp_keyid self.hashes = hashes + self.profile = profile self.loaded_manifests = {} self.updated_manifests = set() @@ -859,14 +865,16 @@ class ManifestRecursiveLoader(object): if fpath in manifest_filenames: continue if fpath in new_manifests: - cls = gemato.manifest.ManifestEntryMANIFEST + ftype = 'MANIFEST' manifest_stack.append((fpath, relpath, self.loaded_manifests[fpath])) else: - cls = gemato.manifest.ManifestEntryDATA + ftype = self.profile.get_entry_type_for_path( + fpath) # note: .path needs to be corrected below - fe = cls(fpath, 0, {}) + fe = gemato.manifest.new_manifest_entry(ftype, + fpath, 0, {}) new_entries.append(fe) changed = gemato.verify.update_entry_for_path( @@ -894,7 +902,19 @@ class ManifestRecursiveLoader(object): mm.entries.append(fe) self.updated_manifests.add(mmpath) else: - fe.path = os.path.relpath(fe.path, mdirpath) + if ftype == 'AUX': + # AUX has implicit files/ prefix in .path + # but for now, we've shoved our path + # into .aux_path + fe.path = os.path.relpath(fe.aux_path, + mdirpath) + assert gemato.util.path_inside_dir( + fe.path, 'files') + # drop files/ prefix for the entry + fe.aux_path = os.path.relpath(fe.path, + 'files') + else: + fe.path = os.path.relpath(fe.path, mdirpath) m.entries.append(fe) self.updated_manifests.add(mpath) diff --git a/tests/test_profile.py b/tests/test_profile.py new file mode 100644 index 0000000..ebc9406 --- /dev/null +++ b/tests/test_profile.py @@ -0,0 +1,131 @@ +# gemato: Profile behavior tests +# vim:fileencoding=utf-8 +# (c) 2017 Michał Górny +# Licensed under the terms of 2-clause BSD license + +import os.path + +import gemato.profile + +from tests.testutil import TempDirTestCase + + +class EbuildRepositoryTests(TempDirTestCase): + """ + Tests for ebuild repository profiles. + """ + + PROFILE = gemato.profile.EbuildRepositoryProfile + DIRS = [ + 'dev-foo', + 'dev-foo/bar', + 'dev-foo/bar/files', + 'eclass', + 'eclass/tests', + 'licenses', + 'metadata', + 'metadata/dtd', + 'metadata/glsa', + 'metadata/install-qa-check.d', + 'metadata/md5-cache', + 'metadata/md5-cache/dev-foo', + 'metadata/news', + 'metadata/news/2020-01-01-foo', + 'metadata/xml-schema', + 'profiles', + 'profiles/arch', + 'profiles/arch/foo', + 'profiles/desc', + 'profiles/updates', + ] + EXPECTED_TYPES = { + 'header.txt': 'DATA', + 'skel.ebuild': 'DATA', + 'skel.metadata.xml': 'DATA', + 'dev-foo/metadata.xml': 'DATA', + 'dev-foo/bar/bar-1.ebuild': 'DATA', + 'dev-foo/bar/metadata.xml': 'DATA', + 'dev-foo/bar/files/test.patch': 'DATA', + 'eclass/foo.eclass': 'DATA', + 'eclass/tests/foo.sh': 'DATA', + 'licenses/foo': 'DATA', + 'metadata/layout.conf': 'DATA', + 'metadata/projects.xml': 'DATA', + 'metadata/pkg_desc_index': 'DATA', + 'metadata/timestamp': 'DATA', + 'metadata/timestamp.chk': 'DATA', + 'metadata/timestamp.commit': 'DATA', + 'metadata/timestamp.x': 'DATA', + 'metadata/dtd/foo.dtd': 'DATA', + 'metadata/glsa/glsa-202001-01.xml': 'DATA', + 'metadata/install-qa-check.d/50foo': 'DATA', + 'metadata/md5-cache/dev-foo/bar-1': 'DATA', + 'metadata/news/2020-01-01-foo/2020-01-01-foo.en.txt': 'DATA', + 'metadata/news/2020-01-01-foo/2020-01-01-foo.en.txt.asc': 'DATA', + 'metadata/xml-schema/foo.xsd': 'DATA', + 'profiles/arch.desc': 'DATA', + 'profiles/categories': 'DATA', + 'profiles/eapi': 'DATA', + 'profiles/info_pkgs': 'DATA', + 'profiles/info_vars': 'DATA', + 'profiles/license_groups': 'DATA', + 'profiles/package.mask': 'DATA', + 'profiles/profiles.desc': 'DATA', + 'profiles/repo_name': 'DATA', + 'profiles/thirdpartymirrors': 'DATA', + 'profiles/use.desc': 'DATA', + 'profiles/use.local.desc': 'DATA', + 'profiles/arch/foo/eapi': 'DATA', + 'profiles/arch/foo/parent': 'DATA', + 'profiles/desc/foo.desc': 'DATA', + 'profiles/updates/1Q-2020': 'DATA', + } + FILES = dict.fromkeys(EXPECTED_TYPES, u'') + + def test_get_entry_type_for_path(self): + p = self.PROFILE() + for f, expt in self.EXPECTED_TYPES.items(): + self.assertEqual( + p.get_entry_type_for_path(f), + expt, + "type mismatch for {}".format(f)) + + def test_update_entries_for_directory(self): + m = gemato.recursiveloader.ManifestRecursiveLoader( + os.path.join(self.dir, 'Manifest'), + hashes=['SHA256', 'SHA512'], + allow_create=True, + profile=self.PROFILE()) + m.update_entries_for_directory('') + for f, expt in self.EXPECTED_TYPES.items(): + self.assertEqual( + m.find_path_entry(f).tag, + expt, + "type mismatch for {}".format(f)) + return m + +class BackwardsCompatEbuildRepositoryTests(EbuildRepositoryTests): + PROFILE = gemato.profile.BackwardsCompatEbuildRepositoryProfile + + def __init__(self, *args, **kwargs): + self.EXPECTED_TYPES = self.EXPECTED_TYPES.copy() + self.EXPECTED_TYPES.update({ + 'dev-foo/bar/bar-1.ebuild': 'EBUILD', + 'dev-foo/bar/metadata.xml': 'MISC', + 'dev-foo/bar/files/test.patch': 'AUX', + }) + # TODO: this is only temporary until we have API to create + # the Manifest at this level automatically + self.FILES['dev-foo/bar/Manifest'] = u'' + super(BackwardsCompatEbuildRepositoryTests, self).__init__( + *args, **kwargs) + + def test_update_entries_for_directory(self): + m = (super(BackwardsCompatEbuildRepositoryTests, self) + .test_update_entries_for_directory()) + self.assertEqual( + m.find_path_entry('dev-foo/bar/files/test.patch').path, + 'files/test.patch') + self.assertEqual( + m.find_path_entry('dev-foo/bar/files/test.patch').aux_path, + 'test.patch') -- cgit v1.2.3