Skip to content

Commit

Permalink
Add class for representing extra files in the compose
Browse files Browse the repository at this point in the history
JIRA: COMPOSE-3829
Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
  • Loading branch information
lubomir committed Oct 25, 2019
1 parent f17dd17 commit 017d830
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 0 deletions.
91 changes: 91 additions & 0 deletions productmd/extra_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-

# Copyright (C) 2019 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, see <https://gnu.org/licenses/>.

import json
import os

import productmd.common
from productmd.common import Header, RPM_ARCHES
from productmd.composeinfo import Compose

__all__ = (
"ExtraFiles",
)


class ExtraFiles(productmd.common.MetadataBase):
def __init__(self):
super(ExtraFiles, self).__init__()
self.header = Header(self, "productmd.extra_files")
self.compose = Compose(self)
self.extra_files = {}

def __getitem__(self, variant):
return self.extra_files[variant]

def __delitem__(self, variant):
del self.extra_files[variant]

def serialize(self, parser):
self.validate()
data = parser
self.header.serialize(data)
data["payload"] = {}
self.compose.serialize(data["payload"])
data["payload"]["extra_files"] = self.extra_files
return data

def deserialize(self, data):
self.header.deserialize(data)
self.compose.deserialize(data["payload"])
self.extra_files = data["payload"]["extra_files"]
self.validate()

def add(self, variant, arch, path, size, checksums):
if not variant:
raise ValueError("Non-empty variant is expected")

if arch not in RPM_ARCHES:
raise ValueError("Arch not found in RPM_ARCHES: %s" % arch)

if not path:
raise ValueError("Path can not be empty.")

if path.startswith("/"):
raise ValueError("Relative path expected: %s" % path)

if not isinstance(checksums, dict):
raise TypeError("Checksums must be a dict.")

metadata = self.extra_files.setdefault(variant, {}).setdefault(arch, [])
metadata.append({"file": path, "size": size, "checksums": checksums})

def dump_for_tree(self, output, variant, arch, basepath):
"""Dump the serialized metadata for given tree. The basepath is
stripped from all paths.
"""
metadata = {"header": {"version": "1.0"}, "data": []}
for item in self.extra_files[variant][arch]:
metadata["data"].append(
{
"file": os.path.relpath(item["file"], basepath),
"size": item["size"],
"checksums": item["checksums"],
}
)

json.dump(metadata, output, sort_keys=True, indent=4, separators=(",", ": "))
137 changes: 137 additions & 0 deletions tests/test_extra_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-

# Copyright (C) 2019 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, see <https://gnu.org/licenses/>.


import unittest

import json
import os
import sys
import tempfile
import shutil

from six import StringIO

DIR = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(DIR, ".."))

from productmd.extra_files import ExtraFiles # noqa


class TestExtraFiles(unittest.TestCase):
def setUp(self):
self.maxDiff = None
self.tmp_dir = tempfile.mkdtemp()

def tearDown(self):
shutil.rmtree(self.tmp_dir)

def assertSameFiles(self, path1, path2):
self.assertEqual(os.path.getsize(path1), os.path.getsize(path2))
with open(path1, "r") as file1:
with open(path2, "r") as file2:
self.assertEqual(file1.read(), file2.read())

def _test_identity(self, modules):
first = os.path.join(self.tmp_dir, "first")
second = os.path.join(self.tmp_dir, "second")

# write original file
modules.dump(first)

# read file and write it back
modules = ExtraFiles()
modules.load(first)
modules.dump(second)

# check if first and second files are identical
self.assertSameFiles(first, second)

def test_bad_checksums(self):
metadata = ExtraFiles()
with self.assertRaises(TypeError):
metadata.add("Everything", "x86_64", "path/to/file", size=1, checksums="no")

def test_bad_variant(self):
metadata = ExtraFiles()
with self.assertRaises(ValueError):
metadata.add("", "x86_64", "path/to/file", size=1, checksums={})

def test_bad_arch(self):
metadata = ExtraFiles()
with self.assertRaises(ValueError):
metadata.add("Everything", "foobar", "path/to/file", size=1, checksums={})

def test_bad_path(self):
metadata = ExtraFiles()
with self.assertRaises(ValueError):
metadata.add("Everything", "foobar", "", size=1, checksums={})

def test_absolute_path(self):
metadata = ExtraFiles()
with self.assertRaises(ValueError):
metadata.add("Everything", "foobar", "/path", size=1, checksums={})

def test_fedora_20(self):
metadata = ExtraFiles()
metadata.header.version = "1.0"
metadata.compose.id = "Fedora-20-20131212.0"
metadata.compose.type = "production"
metadata.compose.date = "20131212"
metadata.compose.respin = 0

metadata.add(
"Everything",
"x86_64",
"compose/Everything/x86_64/os/GPL",
size=123,
checksums={"md5": "abcde", "sha512": "a1b2c3"},
)

self._test_identity(metadata)

def test_partial_dump(self):
metadata = ExtraFiles()
metadata.header.version = "1.0"
metadata.compose.id = "Fedora-20-20131212.0"
metadata.compose.type = "production"
metadata.compose.date = "20131212"
metadata.compose.respin = 0

metadata.add(
"Everything",
"x86_64",
"compose/Everything/x86_64/os/GPL",
size=123,
checksums={"md5": "abcde", "sha512": "a1b2c3"},
)

out = StringIO()
metadata.dump_for_tree(out, "Everything", "x86_64", "compose/Everything/x86_64/os")
self.assertEqual(
json.loads(out.getvalue()),
{
"header": {"version": "1.0"},
"data": [
{
"file": "GPL",
"size": 123,
"checksums": {"md5": "abcde", "sha512": "a1b2c3"},
},
],
},
)

0 comments on commit 017d830

Please sign in to comment.