From 9bd7692f7fe9d9bf2962f546d1034dcf83247fde Mon Sep 17 00:00:00 2001 From: Petr Šabata Date: Aug 17 2017 09:21:57 +0000 Subject: Support for context, architecture and EOLs Adding three new values to the specification: * context uniquely identifies a specific module build among other variants with the same stream, name and version * arch specifies the content hardware architecture binary compatibility; note it's not the "basearch" * eol is a date of when the module goes EOL This commit includes code changes to cover these additions. Signed-off-by: Petr Šabata --- diff --git a/modulemd/__init__.py b/modulemd/__init__.py index 839604b..a841c71 100644 --- a/modulemd/__init__.py +++ b/modulemd/__init__.py @@ -41,6 +41,8 @@ Example usage: """ import sys +import datetime +import dateutil.parser import yaml if sys.version_info > (3,): @@ -109,8 +111,11 @@ class ModuleMetadata(object): self.name = "" self.stream = "" self.version = 0 + self.context = "" + self.arch = "" self.summary = "" self.description = "" + self.eol = None self.module_licenses = set() self.content_licenses = set() self.buildrequires = dict() @@ -132,27 +137,33 @@ class ModuleMetadata(object): "name: {1}, " "stream: {2}, " "version: {3}, " - "summary: {4}, " - "description: {5}, " - "module_licenses: {6}, " - "content_licenses: {7}, " - "buildrequires: {8}, " - "requires: {9}, " - "community: {10}, " - "documentation: {11}, " - "tracker: {12}, " - "xmd: {13}, " - "profiles: {14}, " - "api: {15}, " - "filter: {16}, " - "components: {17}, " - "artifacts: {18}>").format( + "context: {4}, " + "arch: {5}, " + "summary: {6}, " + "description: {7}, " + "eol: {8}, " + "module_licenses: {9}, " + "content_licenses: {10}, " + "buildrequires: {11}, " + "requires: {12}, " + "community: {13}, " + "documentation: {14}, " + "tracker: {15}, " + "xmd: {16}, " + "profiles: {17}, " + "api: {18}, " + "filter: {19}, " + "components: {20}, " + "artifacts: {21}>").format( repr(self.mdversion), repr(self.name), repr(self.stream), repr(self.version), + repr(self.context), + repr(self.arch), repr(self.summary), repr(self.description), + repr(self.eol), repr(sorted(self.module_licenses)), repr(sorted(self.content_licenses)), repr(sorted(self.buildrequires)), @@ -209,10 +220,19 @@ class ModuleMetadata(object): self.stream = str(data["stream"]) if "version" in data: self.version = int(data["version"]) + if "context" in data: + self.context = str(data["context"]) + if "arch" in data: + self.arch = str(data["arch"]) if "summary" in data: self.summary = str(data["summary"]) if "description" in data: self.description = str(data["description"]) + if "eol" in data: + try: + self.eol = dateutil.parser.parse(str(data["eol"])).date() + except: + self.eol = None if ("license" in data and isinstance(data["license"], dict) and "module" in data["license"] @@ -338,8 +358,14 @@ class ModuleMetadata(object): d["stream"] = self.stream if self.version: d["version"] = self.version + if self.context: + d["context"] = self.context + if self.arch: + d["arch"] = self.arch d["summary"] = self.summary d["description"] = self.description + if self.eol: + d["eol"] = str(self.eol) d["license"] = dict() d["license"]["module"] = sorted(list(self.module_licenses)) if self.content_licenses: @@ -489,6 +515,30 @@ class ModuleMetadata(object): self._version = i @property + def context(self): + """A string property representing the context flag of the module.""" + return self._context + + @context.setter + def context(self, s): + if not isinstance(s, str): + raise TypeError("context: data type not supported") + self._context = s + + @property + def arch(self): + """A string property representing the module artifacts' hardware + architecture compatibility. + """ + return self._arch + + @arch.setter + def arch(self, s): + if not isinstance(s, str): + raise TypeError("arch: data type not supported") + self._arch = s + + @property def summary(self): """A string property representing a short summary of the module.""" return self._summary @@ -512,6 +562,18 @@ class ModuleMetadata(object): self._description = s @property + def eol(self): + """A datetime.date property representing the module's EOL date. + May be None if no EOL is defined.""" + return self._eol + + @eol.setter + def eol(self, o): + if not isinstance(o, datetime.date) and o is not None: + raise TypeError("eol: data type not supported") + self._eol = o + + @property def module_licenses(self): """A set of strings, a property, representing the license terms of the module itself.""" diff --git a/modulemd/tests/test_basic.py b/modulemd/tests/test_basic.py index 96d411f..71bc3d7 100644 --- a/modulemd/tests/test_basic.py +++ b/modulemd/tests/test_basic.py @@ -28,6 +28,7 @@ import unittest import os import sys +import datetime DIR = os.path.dirname(__file__) sys.path.insert(0, os.path.join(DIR, "..")) @@ -45,8 +46,11 @@ class TestBasic(unittest.TestCase): m.name = "name" m.stream = "stream" m.version = 1 + m.context = "context" + m.arch = "arch" m.summary = "summary" m.description = "description" + m.eol = datetime.date(2077, 10, 23) m.module_licenses = set(["module1", "module2", "module3"]) m.content_licenses = set(["content1", "content2", "content3"]) m.xmd = { "key" : "value" } @@ -100,12 +104,21 @@ class TestBasic(unittest.TestCase): def test_version(self): self.assertEqual(self.mmd.version, 1) + def test_context(self): + self.assertEqual(self.mmd.context, "context") + + def test_arch(self): + self.assertEqual(self.mmd.arch, "arch") + def test_summary(self): self.assertEqual(self.mmd.summary, "summary") def test_description(self): self.assertEqual(self.mmd.description, "description") + def test_eol(self): + self.assertEqual(self.mmd.eol, datetime.date(2077, 10, 23)) + def test_module_licenses(self): self.assertSetEqual( self.mmd.module_licenses, diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py index fc82fb0..c420eb9 100644 --- a/modulemd/tests/test_io.py +++ b/modulemd/tests/test_io.py @@ -28,6 +28,7 @@ import unittest import os import sys +import datetime DIR = os.path.dirname(__file__) sys.path.insert(0, os.path.join(DIR, "..")) @@ -47,8 +48,11 @@ class TestIO(unittest.TestCase): self.assertEqual(self.mmd.name, "foo") self.assertEqual(self.mmd.stream, "stream-name") self.assertEqual(self.mmd.version, 20160927144203) + self.assertEqual(self.mmd.context, "c0ffee43") + self.assertEqual(self.mmd.arch, "x86_64") self.assertEqual(self.mmd.summary, "An example module") self.assertEqual(self.mmd.description, "A module for the demonstration of the metadata format. Also, the obligatory lorem ipsum dolor sit amet goes right here.") + self.assertEqual(self.mmd.eol, datetime.date(2077, 10, 23)) self.assertSetEqual(self.mmd.module_licenses, set(["MIT"])) self.assertSetEqual(self.mmd.content_licenses, set(["Beerware", "GPLv2+", "zlib"])) diff --git a/modulemd/tests/test_validation.py b/modulemd/tests/test_validation.py index e6ba62b..19abc8f 100644 --- a/modulemd/tests/test_validation.py +++ b/modulemd/tests/test_validation.py @@ -62,12 +62,21 @@ class TestValidation(unittest.TestCase): def test_version_value(self): self.assertRaises(ValueError, setattr, self.mmd, "version", -1) + def test_context_type(self): + self.assertRaises(TypeError, setattr, self.mmd, "context", 0) + + def test_arch_type(self): + self.assertRaises(TypeError, setattr, self.mmd, "arch", 0) + def test_summary_type(self): self.assertRaises(TypeError, setattr, self.mmd, "summary", 0) def test_description_type(self): self.assertRaises(TypeError, setattr, self.mmd, "description", 0) + def test_eol_type(self): + self.assertRaises(TypeError, setattr, self.mmd, "eol", 0) + def test_module_license_type(self): self.assertRaises(TypeError, setattr, self.mmd, "module_licenses", 0) diff --git a/spec.yaml b/spec.yaml index 480a6a0..cf9b790 100644 --- a/spec.yaml +++ b/spec.yaml @@ -16,18 +16,40 @@ data: # timestamp. Module version defines upgrade path for the particular # update stream. version: 20160927144203 + # Module context flag, optional + # The context flag serves to distinguish module builds with the + # same name, stream and version and plays an important role in + # future automatic module stream name expansion. + # Filled in by the buildsystem. A short hash of the module's name, + # stream, version and its expanded runtime dependencies. + context: c0ffee43 + # Module artifact architecture, optional + # Contains a string describing the module's artifacts' main hardware + # architecture compatibility, distinguishing the module artifact, + # e.g. a repository, from others with the same name, stream, version and + # context. This is not a generic hardware family (i.e. basearch). + # Examples: i386, i486, armv7hl, x86_64 + # Filled in by the buildsystem during the compose stage. + arch: x86_64 # A short summary describing the module, required summary: An example module # A verbose description of the module, required description: >- A module for the demonstration of the metadata format. Also, the obligatory lorem ipsum dolor sit amet goes right here. + # The end of life, aka Best Before, optional. + # A UTC date in the ISO 8601 format signifying the day when this + # module goes EOL, i.e. starting the day below, this module won't + # receive any more updates. Typically defined in an external data + # source and filled in by the buildsystem. + eol: 2077-10-23 # Module and content licenses in the Fedora license identifier # format, required license: # Module license, required - # This list covers licenses used for the module metadata, SPEC - # files or extra patches + # This list covers licenses used for the module metadata and + # possibly other files involved in the creation of this specific + # module. module: - MIT # Content license, optional @@ -184,11 +206,15 @@ data: xxx: rationale: xxx demonstrates arches and multilib. # xxx is only available on the listed architectures. + # TODO: This needs a better definition of what the + # architectures here actually mean. # Optional, defaults to all available arches. arches: [ i686, x86_64 ] # A list of architectures with multilib # installs, i.e. both i686 and x86_64 # versions will be installed on x86_64. + # TODO: This needs to be reworked or dropped as it's + # not at all useful in the current state. # Optional, defaults to no multilib. multilib: [ x86_64 ] xyz: