Blob Blame History Raw
From a48db44b73785b5d5fbe8ae827522695fa0fd9ce Mon Sep 17 00:00:00 2001
From: Aleš Matěj <amatej@redhat.com>
Date: Tue, 8 Jan 2019 15:44:55 +0100
Subject: [PATCH] Add support for modular errata (RhBug:1656584)

---
 src/python/CMakeLists.txt                         |   1 +
 src/python/__init__.py                            |   3 +++
 src/python/createrepo_cmodule.c                   |   8 ++++++++
 src/python/updatecollection-py.c                  |  43 +++++++++++++++++++++++++++++++++++++++++++
 src/python/updatecollectionmodule-py.c            | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/python/updatecollectionmodule-py.h            |  33 +++++++++++++++++++++++++++++++++
 src/updateinfo.c                                  |  44 ++++++++++++++++++++++++++++++++++++++++++++
 src/updateinfo.h                                  |  24 ++++++++++++++++++++++++
 src/xml_dump_updateinfo.c                         |  20 ++++++++++++++++++++
 src/xml_parser_internal.h                         |   2 ++
 src/xml_parser_updateinfo.c                       |  54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/fixtures.h                                  |   1 +
 tests/python/tests/test_updatecollection.py       |  16 ++++++++++++++++
 tests/python/tests/test_updatecollectionmodule.py |  31 +++++++++++++++++++++++++++++++
 tests/python/tests/test_updateinfo.py             | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test_xml_parser_updateinfo.c                |  86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/testdata/updateinfo_files/updateinfo_03.xml | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 17 files changed, 957 insertions(+)
 create mode 100644 src/python/updatecollectionmodule-py.c
 create mode 100644 src/python/updatecollectionmodule-py.h
 create mode 100644 tests/python/tests/test_updatecollectionmodule.py
 create mode 100644 tests/testdata/updateinfo_files/updateinfo_03.xml

diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt
index 9f1ac64..ebf4d4c 100644
--- a/src/python/CMakeLists.txt
+++ b/src/python/CMakeLists.txt
@@ -50,6 +50,7 @@ SET (createrepo_cmodule_SRCS
      sqlite-py.c
      typeconversion.c
      updatecollection-py.c
+     updatecollectionmodule-py.c
      updatecollectionpackage-py.c
      updateinfo-py.c
      updaterecord-py.c
diff --git a/src/python/__init__.py b/src/python/__init__.py
index 6c29e74..65d7f82 100644
--- a/src/python/__init__.py
+++ b/src/python/__init__.py
@@ -206,6 +206,9 @@ class OtherSqlite(Sqlite):
 
 UpdateCollection = _createrepo_c.UpdateCollection
 
+# UpdateCollectionModule class
+
+UpdateCollectionModule = _createrepo_c.UpdateCollectionModule
 
 # UpdateCollectionPackage class
 
diff --git a/src/python/createrepo_cmodule.c b/src/python/createrepo_cmodule.c
index fe4d2ad..9be5f46 100644
--- a/src/python/createrepo_cmodule.c
+++ b/src/python/createrepo_cmodule.c
@@ -35,6 +35,7 @@
 #include "repomdrecord-py.h"
 #include "sqlite-py.h"
 #include "updatecollection-py.h"
+#include "updatecollectionmodule-py.h"
 #include "updatecollectionpackage-py.h"
 #include "updateinfo-py.h"
 #include "updaterecord-py.h"
@@ -185,6 +186,13 @@ init_createrepo_c(void)
     PyModule_AddObject(m, "UpdateCollection",
                        (PyObject *)&UpdateCollection_Type);
 
+    /* _createrepo_c.UpdateCollectionModule */
+    if (PyType_Ready(&UpdateCollectionModule_Type) < 0)
+        return FAILURE;
+    Py_INCREF(&UpdateCollectionModule_Type);
+    PyModule_AddObject(m, "UpdateCollectionModule",
+                       (PyObject *)&UpdateCollectionModule_Type);
+
     /* _createrepo_c.UpdateCollectionPackage */
     if (PyType_Ready(&UpdateCollectionPackage_Type) < 0)
         return FAILURE;
diff --git a/src/python/updatecollection-py.c b/src/python/updatecollection-py.c
index 3a791be..ca97657 100644
--- a/src/python/updatecollection-py.c
+++ b/src/python/updatecollection-py.c
@@ -22,6 +22,7 @@
 #include <stddef.h>
 
 #include "updatecollection-py.h"
+#include "updatecollectionmodule-py.h"
 #include "updatecollectionpackage-py.h"
 #include "exception-py.h"
 #include "typeconversion.h"
@@ -188,6 +189,13 @@ typedef int (*ConversionToCheckFunc)(PyObject *);
 typedef void *(*ConversionToFunc)(PyObject *, GStringChunk *);
 
 PyObject *
+PyObject_FromUpdateCollectionModule(cr_UpdateCollectionModule *module)
+{
+    return Object_FromUpdateCollectionModule(
+                        cr_updatecollectionmodule_copy(module));
+}
+
+PyObject *
 PyObject_FromUpdateCollectionPackage(cr_UpdateCollectionPackage *pkg)
 {
     return Object_FromUpdateCollectionPackage(
@@ -249,6 +257,23 @@ get_list(_UpdateCollectionObject *self, void *conv)
     return list;
 }
 
+static PyObject *
+get_module(_UpdateCollectionObject *self, void *member_offset)
+{
+    if (check_UpdateCollectionStatus(self))
+        return NULL;
+
+    cr_UpdateCollection *collection = self->collection;
+
+    cr_UpdateCollectionModule *module = *((cr_UpdateCollectionModule **) ((size_t) collection + (size_t) member_offset));
+    if (module == NULL)
+        Py_RETURN_NONE;
+
+    PyObject *py_module = PyObject_FromUpdateCollectionModule(module);
+
+    return py_module;
+}
+
 static int
 set_str(_UpdateCollectionObject *self, PyObject *value, void *member_offset)
 {
@@ -265,11 +290,29 @@ set_str(_UpdateCollectionObject *self, PyObject *value, void *member_offset)
     return 0;
 }
 
+static int
+set_module(_UpdateCollectionObject *self, PyObject *value, void *member_offset)
+{
+    if (check_UpdateCollectionStatus(self))
+        return -1;
+    if (!UpdateCollectionModuleObject_Check(value) && value != Py_None) {
+        PyErr_SetString(PyExc_TypeError, "Module or None expected!");
+        return -1;
+    }
+    cr_UpdateCollectionModule *module = UpdateCollectionModule_FromPyObject(value);
+    cr_UpdateCollection *collection = self->collection;
+    *((cr_UpdateCollectionModule **) ((size_t) collection + (size_t) member_offset)) = module;
+
+    return 0;
+}
+
 static PyGetSetDef updatecollection_getsetters[] = {
     {"shortname",     (getter)get_str, (setter)set_str,
         "Short name", OFFSET(shortname)},
     {"name",          (getter)get_str, (setter)set_str,
         "Name of the collection", OFFSET(name)},
+    {"module",        (getter)get_module, (setter)set_module,
+        "Module information", OFFSET(module)},
     {"packages",       (getter)get_list, (setter)NULL,
         "List of packages", &(list_convertors[0])},
     {NULL, NULL, NULL, NULL, NULL} /* sentinel */
diff --git a/src/python/updatecollectionmodule-py.c b/src/python/updatecollectionmodule-py.c
new file mode 100644
index 0000000..20b2d99
--- /dev/null
+++ b/src/python/updatecollectionmodule-py.c
@@ -0,0 +1,274 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "updatecollectionmodule-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+#include "contentstat-py.h"
+
+typedef struct {
+    PyObject_HEAD
+    cr_UpdateCollectionModule *module;
+} _UpdateCollectionModuleObject;
+
+PyObject *
+Object_FromUpdateCollectionModule(cr_UpdateCollectionModule *mod)
+{
+    PyObject *py_rec;
+
+    if (!mod) {
+        PyErr_SetString(PyExc_ValueError, "Expected a cr_UpdateCollectionModule pointer not NULL.");
+        return NULL;
+    }
+
+    py_rec = PyObject_CallObject((PyObject *) &UpdateCollectionModule_Type, NULL);
+    cr_updatecollectionmodule_free(((_UpdateCollectionModuleObject *)py_rec)->module);
+    ((_UpdateCollectionModuleObject *)py_rec)->module = mod;
+
+    return py_rec;
+}
+
+cr_UpdateCollectionModule *
+UpdateCollectionModule_FromPyObject(PyObject *o)
+{
+    if (!UpdateCollectionModuleObject_Check(o)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a UpdateCollectionModule object.");
+        return NULL;
+    }
+    return ((_UpdateCollectionModuleObject *)o)->module;
+}
+
+static int
+check_UpdateCollectionModuleStatus(const _UpdateCollectionModuleObject *self)
+{
+    assert(self != NULL);
+    assert(UpdateCollectionModuleObject_Check(self));
+    if (self->module == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c UpdateCollectionModule object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+updatecollectionmodule_new(PyTypeObject *type,
+                    G_GNUC_UNUSED PyObject *args,
+                    G_GNUC_UNUSED PyObject *kwds)
+{
+    _UpdateCollectionModuleObject *self = (_UpdateCollectionModuleObject *)type->tp_alloc(type, 0);
+    if (self) {
+        self->module = NULL;
+    }
+    return (PyObject *)self;
+}
+
+PyDoc_STRVAR(updatecollectionmodule_init__doc__,
+".. method:: __init__()\n\n");
+
+static int
+updatecollectionmodule_init(_UpdateCollectionModuleObject *self,
+                     G_GNUC_UNUSED PyObject *args,
+                     G_GNUC_UNUSED PyObject *kwds)
+{
+    /* Free all previous resources when reinitialization */
+    if (self->module)
+        cr_updatecollectionmodule_free(self->module);
+
+    /* Init */
+    self->module = cr_updatecollectionmodule_new();
+    if (self->module == NULL) {
+        PyErr_SetString(CrErr_Exception, "UpdateCollectionModule initialization failed");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+updatecollectionmodule_dealloc(_UpdateCollectionModuleObject *self)
+{
+    if (self->module)
+        cr_updatecollectionmodule_free(self->module);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+updatecollectionmodule_repr(G_GNUC_UNUSED _UpdateCollectionModuleObject *self)
+{
+    return PyUnicode_FromFormat("<createrepo_c.UpdateCollectionModule object>");
+}
+
+/* UpdateCollectionModule methods */
+
+PyDoc_STRVAR(copy__doc__,
+"copy() -> UpdateCollectionModule\n\n"
+"Return copy of the UpdateCollectionModule object");
+
+static PyObject *
+copy_updatecollectionmodule(_UpdateCollectionModuleObject *self,
+                             G_GNUC_UNUSED void *nothing)
+{
+    if (check_UpdateCollectionModuleStatus(self))
+        return NULL;
+    return Object_FromUpdateCollectionModule(cr_updatecollectionmodule_copy(self->module));
+}
+
+static struct PyMethodDef updatecollectionmodule_methods[] = {
+    {"copy", (PyCFunction)copy_updatecollectionmodule, METH_NOARGS,
+        copy__doc__},
+    {NULL} /* sentinel */
+};
+
+/* getsetters */
+
+#define OFFSET(member) (void *) offsetof(cr_UpdateCollectionModule, member)
+
+static PyObject *
+get_str(_UpdateCollectionModuleObject *self, void *member_offset)
+{
+    if (check_UpdateCollectionModuleStatus(self))
+        return NULL;
+    cr_UpdateCollectionModule *module = self->module;
+    char *str = *((char **) ((size_t) module + (size_t) member_offset));
+    if (str == NULL)
+        Py_RETURN_NONE;
+    return PyUnicode_FromString(str);
+}
+
+static PyObject *
+get_uint(_UpdateCollectionModuleObject *self, void *member_offset)
+{
+    if (check_UpdateCollectionModuleStatus(self))
+        return NULL;
+    cr_UpdateCollectionModule *module = self->module;
+    guint64 val = *((guint64 *) ((size_t) module + (size_t) member_offset));
+    return PyLong_FromUnsignedLongLong((guint64) val);
+}
+
+static int
+set_str(_UpdateCollectionModuleObject *self, PyObject *value, void *member_offset)
+{
+    if (check_UpdateCollectionModuleStatus(self))
+        return -1;
+    if (!PyUnicode_Check(value) && !PyBytes_Check(value) && value != Py_None) {
+        PyErr_SetString(PyExc_TypeError, "Unicode, bytes, or None expected!");
+        return -1;
+    }
+
+    if (PyUnicode_Check(value)) {
+        value = PyUnicode_AsUTF8String(value);
+    }
+
+    cr_UpdateCollectionModule *module = self->module;
+    char *str = cr_safe_string_chunk_insert(module->chunk,
+                                            PyObject_ToStrOrNull(value));
+
+    *((char **) ((size_t) module + (size_t) member_offset)) = str;
+    return 0;
+}
+
+static int
+set_uint(_UpdateCollectionModuleObject *self, PyObject *value, void *member_offset)
+{
+    if (check_UpdateCollectionModuleStatus(self))
+        return -1;
+    guint64 val;
+
+    if (PyLong_Check(value)) {
+        val = PyLong_AsUnsignedLongLong(value);
+    } else if (PyFloat_Check(value)) {
+        val = (guint64) PyFloat_AS_DOUBLE(value);
+#if PY_MAJOR_VERSION < 3
+    } else if (PyInt_Check(value)) {
+        val = PyInt_AS_LONG(value);
+#endif
+    } else {
+        PyErr_SetString(PyExc_TypeError, "Number expected!");
+        return -1;
+    }
+
+    cr_UpdateCollectionModule *module = self->module;
+    *((guint64 *) ((size_t) module + (size_t) member_offset)) = (guint64) val;
+    return 0;
+}
+
+static PyGetSetDef updatecollectionmodule_getsetters[] = {
+    {"name",               (getter)get_str, (setter)set_str,
+        "Name",            OFFSET(name)},
+    {"stream",             (getter)get_str, (setter)set_str,
+        "Stream",          OFFSET(stream)},
+    {"version",            (getter)get_uint, (setter)set_uint,
+        "Version",         OFFSET(version)},
+    {"context",            (getter)get_str, (setter)set_str,
+        "Context",         OFFSET(context)},
+    {"arch",               (getter)get_str, (setter)set_str,
+        "Arch",            OFFSET(arch)},
+    {NULL, NULL, NULL, NULL, NULL} /* sentinel */
+};
+
+/* Object */
+
+PyTypeObject UpdateCollectionModule_Type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "createrepo_c.UpdateCollectionModule", /* tp_name */
+    sizeof(_UpdateCollectionModuleObject), /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor) updatecollectionmodule_dealloc, /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc) updatecollectionmodule_repr,/* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    updatecollectionmodule_init__doc__,    /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    updatecollectionmodule_methods,        /* tp_methods */
+    0,                              /* tp_members */
+    updatecollectionmodule_getsetters,     /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc) updatecollectionmodule_init,/* tp_init */
+    0,                              /* tp_alloc */
+    updatecollectionmodule_new,            /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/updatecollectionmodule-py.h b/src/python/updatecollectionmodule-py.h
new file mode 100644
index 0000000..5847259
--- /dev/null
+++ b/src/python/updatecollectionmodule-py.h
@@ -0,0 +1,33 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_UPDATECOLLECTIONMODULE_PY_H
+#define CR_UPDATECOLLECTIONMODULE_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject UpdateCollectionModule_Type;
+
+#define UpdateCollectionModuleObject_Check(o) \
+            PyObject_TypeCheck(o, &UpdateCollectionModule_Type)
+
+PyObject *Object_FromUpdateCollectionModule(cr_UpdateCollectionModule *rec);
+cr_UpdateCollectionModule *UpdateCollectionModule_FromPyObject(PyObject *o);
+
+#endif
diff --git a/src/updateinfo.c b/src/updateinfo.c
index 6e45229..cdc4747 100644
--- a/src/updateinfo.c
+++ b/src/updateinfo.c
@@ -74,6 +74,46 @@ cr_updatecollectionpackage_free(cr_UpdateCollectionPackage *pkg)
 
 
 /*
+ * cr_UpdateCollectionModule
+ */
+
+cr_UpdateCollectionModule *
+cr_updatecollectionmodule_new(void)
+{
+    cr_UpdateCollectionModule *module = g_malloc0(sizeof(*module));
+    module->chunk = g_string_chunk_new(0);
+    return module;
+}
+
+cr_UpdateCollectionModule *
+cr_updatecollectionmodule_copy(const cr_UpdateCollectionModule *orig)
+{
+    cr_UpdateCollectionModule *module;
+
+    if (!orig) return NULL;
+
+    module = cr_updatecollectionmodule_new();
+
+    module->name    = cr_safe_string_chunk_insert(module->chunk, orig->name);
+    module->stream  = cr_safe_string_chunk_insert(module->chunk, orig->stream);
+    module->version = orig->version;
+    module->context = cr_safe_string_chunk_insert(module->chunk, orig->context);
+    module->arch    = cr_safe_string_chunk_insert(module->chunk, orig->arch);
+
+    return module;
+}
+
+void
+cr_updatecollectionmodule_free(cr_UpdateCollectionModule *module)
+{
+    if (!module)
+        return;
+    g_string_chunk_free(module->chunk);
+    g_free(module);
+}
+
+
+/*
  * cr_UpdateCollection
  */
 
@@ -97,6 +137,10 @@ cr_updatecollection_copy(const cr_UpdateCollection *orig)
     col->shortname = cr_safe_string_chunk_insert(col->chunk, orig->shortname);
     col->name      = cr_safe_string_chunk_insert(col->chunk, orig->name);
 
+    if (orig->module) {
+      col->module = cr_updatecollectionmodule_copy(orig->module);
+    }
+
     if (orig->packages) {
         GSList *newlist = NULL;
         for (GSList *elem = orig->packages; elem; elem = g_slist_next(elem)) {
diff --git a/src/updateinfo.h b/src/updateinfo.h
index dbf7807..38883e0 100644
--- a/src/updateinfo.h
+++ b/src/updateinfo.h
@@ -51,8 +51,19 @@ typedef struct {
 } cr_UpdateCollectionPackage;
 
 typedef struct {
+    gchar *name;
+    gchar *stream;
+    guint64 version;
+    gchar *context;
+    gchar *arch;
+
+    GStringChunk *chunk;
+} cr_UpdateCollectionModule;
+
+typedef struct {
     gchar *shortname;   /*!< e.g. rhn-tools-rhel-x86_64-server-6.5.aus */
     gchar *name;        /*!< e.g. RHN Tools for RHEL AUS (v. 6.5 for 64-bit x86_64) */
+    cr_UpdateCollectionModule *module;
     GSList *packages;   /*!< List of cr_UpdateCollectionPackage */
     GStringChunk *chunk;
 } cr_UpdateCollection;
@@ -106,6 +117,19 @@ void
 cr_updatecollectionpackage_free(cr_UpdateCollectionPackage *pkg);
 
 /*
+ * cr_UpdateCollectionModule
+ */
+
+cr_UpdateCollectionModule *
+cr_updatecollectionmodule_new(void);
+
+cr_UpdateCollectionModule *
+cr_updatecollectionmodule_copy(const cr_UpdateCollectionModule *orig);
+
+void
+cr_updatecollectionmodule_free(cr_UpdateCollectionModule *pkg);
+
+/*
  * cr_UpdateCollection
  */
 
diff --git a/src/xml_dump_updateinfo.c b/src/xml_dump_updateinfo.c
index 4fb5720..fafe686 100644
--- a/src/xml_dump_updateinfo.c
+++ b/src/xml_dump_updateinfo.c
@@ -66,6 +66,24 @@ cr_xml_dump_updatecollectionpackages(xmlNodePtr collection, GSList *packages)
 }
 
 void
+cr_xml_dump_updatecollectionmodule(xmlNodePtr collection, cr_UpdateCollectionModule *module)
+{
+    if (!module)
+        return;
+
+    xmlNodePtr xml_module;
+    xml_module = xmlNewChild(collection, NULL, BAD_CAST "module", NULL);
+
+    cr_xmlNewProp_c(xml_module, BAD_CAST "name", BAD_CAST module->name);
+    cr_xmlNewProp_c(xml_module, BAD_CAST "stream", BAD_CAST module->stream);
+    gchar buf[21]; //20 + '\0' is max number of chars of guint64: G_MAXUINT64 (= 18,446,744,073,709,551,615)
+    snprintf(buf, 21, "%" G_GUINT64_FORMAT, module->version);
+    cr_xmlNewProp_c(xml_module, BAD_CAST "version", BAD_CAST buf);
+    cr_xmlNewProp_c(xml_module, BAD_CAST "context", BAD_CAST module->context);
+    cr_xmlNewProp_c(xml_module, BAD_CAST "arch", BAD_CAST module->arch);
+}
+
+void
 cr_xml_dump_updateinforecord_pkglist(xmlNodePtr update, GSList *collections)
 {
     xmlNodePtr pkglist;
@@ -83,6 +101,8 @@ cr_xml_dump_updateinforecord_pkglist(xmlNodePtr update, GSList *collections)
                              BAD_CAST "name",
                              BAD_CAST col->name);
 
+        cr_xml_dump_updatecollectionmodule(collection, col->module);
+
         cr_xml_dump_updatecollectionpackages(collection, col->packages);
     }
 }
diff --git a/src/xml_parser_internal.h b/src/xml_parser_internal.h
index 6b400eb..e079ece 100644
--- a/src/xml_parser_internal.h
+++ b/src/xml_parser_internal.h
@@ -151,6 +151,8 @@ typedef struct _cr_ParserData {
         Update record object */
     cr_UpdateCollection *updatecollection; /*!<
         Update collection object */
+    cr_UpdateCollectionModule *updatecollectionmodule; /*!<
+        Update collection module object */
     cr_UpdateCollectionPackage *updatecollectionpackage; /*!<
         Update collection package object */
 
diff --git a/src/xml_parser_updateinfo.c b/src/xml_parser_updateinfo.c
index 18e5277..c6c6503 100644
--- a/src/xml_parser_updateinfo.c
+++ b/src/xml_parser_updateinfo.c
@@ -54,6 +54,7 @@ typedef enum {
     STATE_PKGLIST,          // <pkglist> ----------------------------
     STATE_COLLECTION,
     STATE_NAME,
+    STATE_MODULE,
     STATE_PACKAGE,
     STATE_FILENAME,
     STATE_SUM,
@@ -89,6 +90,7 @@ static cr_StatesSwitch stateswitches[] = {
     { STATE_PKGLIST,    "collection",        STATE_COLLECTION,        0 },
     { STATE_COLLECTION, "package",           STATE_PACKAGE,           0 },
     { STATE_COLLECTION, "name",              STATE_NAME,              1 },
+    { STATE_COLLECTION, "module",            STATE_MODULE,            0 },
     { STATE_PACKAGE,    "filename",          STATE_FILENAME,          1 },
     { STATE_PACKAGE,    "sum",               STATE_SUM,               1 },
     { STATE_PACKAGE,    "reboot_suggested",  STATE_REBOOTSUGGESTED,   0 },
@@ -141,6 +143,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
     // Shortcuts
     cr_UpdateRecord *rec = pd->updaterecord;
     cr_UpdateCollection *collection = pd->updatecollection;
+    cr_UpdateCollectionModule *module = pd->updatecollectionmodule;
     cr_UpdateCollectionPackage *package = pd->updatecollectionpackage;
 
     switch(pd->state) {
@@ -173,6 +176,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(!pd->updaterecord);
         assert(!pd->updatecollection);
+        assert(!pd->updatecollectionmodule);
         assert(!pd->updatecollectionpackage);
 
         rec = cr_updaterecord_new();
@@ -201,6 +205,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(pd->updaterecord);
         assert(!pd->updatecollection);
+        assert(!pd->updatecollectionmodule);
         assert(!pd->updatecollectionpackage);
         val = cr_find_attr("date", attr);
         if (val)
@@ -211,6 +216,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(pd->updaterecord);
         assert(!pd->updatecollection);
+        assert(!pd->updatecollectionmodule);
         assert(!pd->updatecollectionpackage);
         val = cr_find_attr("date", attr);
         if (val)
@@ -223,6 +229,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(pd->updaterecord);
         assert(!pd->updatecollection);
+        assert(!pd->updatecollectionmodule);
         assert(!pd->updatecollectionpackage);
 
         ref = cr_updatereference_new();
@@ -251,6 +258,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(pd->updaterecord);
         assert(!pd->updatecollection);
+        assert(!pd->updatecollectionmodule);
         assert(!pd->updatecollectionpackage);
 
         collection = cr_updatecollection_new();
@@ -263,6 +271,49 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
 
         break;
 
+    case STATE_MODULE:
+        assert(pd->updateinfo);
+        assert(pd->updaterecord);
+        assert(pd->updatecollection);
+        assert(!pd->updatecollectionmodule);
+        assert(!pd->updatecollectionpackage);
+
+        module = cr_updatecollectionmodule_new();
+        if (module)
+            collection->module = module;
+
+        val = cr_find_attr("name", attr);
+        if (val)
+            module->name = g_string_chunk_insert(module->chunk, val);
+
+        val = cr_find_attr("stream", attr);
+        if (val)
+            module->stream = g_string_chunk_insert(module->chunk, val);
+
+        val = cr_find_attr("version", attr);
+        if (val){
+            gchar *endptr;
+            errno = 0;
+            module->version = strtoull(val, &endptr, 10);
+            if ((errno == ERANGE && (module->version == ULLONG_MAX))
+                 || (errno != 0 && module->version == 0)) {
+                perror("strtoull error when parsing module version");
+                module->version = 0;
+            }
+            if (endptr == val)
+                module->version = 0;
+        }
+
+        val = cr_find_attr("context", attr);
+        if (val)
+            module->context = g_string_chunk_insert(module->chunk, val);
+
+        val = cr_find_attr("arch", attr);
+        if (val)
+            module->arch = g_string_chunk_insert(module->chunk, val);
+
+        break;
+
     case STATE_PACKAGE:
         assert(pd->updateinfo);
         assert(pd->updaterecord);
@@ -303,6 +354,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(pd->updaterecord);
         assert(pd->updatecollection);
+        assert(pd->updatecollectionmodule);
         assert(pd->updatecollectionpackage);
         val = cr_find_attr("type", attr);
         if (val)
@@ -313,6 +365,7 @@ cr_start_handler(void *pdata, const char *element, const char **attr)
         assert(pd->updateinfo);
         assert(pd->updaterecord);
         assert(pd->updatecollection);
+        assert(pd->updatecollectionmodule);
         assert(pd->updatecollectionpackage);
         package->reboot_suggested = TRUE;
         break;
@@ -352,6 +405,7 @@ cr_end_handler(void *pdata, G_GNUC_UNUSED const char *element)
     case STATE_UPDATED:
     case STATE_REFERENCES:
     case STATE_REFERENCE:
+    case STATE_MODULE:
     case STATE_PKGLIST:
     case STATE_REBOOTSUGGESTED:
         // All elements with no text data and without need of any
diff --git a/tests/fixtures.h b/tests/fixtures.h
index ee374f5..8567714 100644
--- a/tests/fixtures.h
+++ b/tests/fixtures.h
@@ -83,5 +83,6 @@
 #define TEST_UPDATEINFO_00      TEST_UPDATEINFO_FILES_PATH"updateinfo_00.xml"
 #define TEST_UPDATEINFO_01      TEST_UPDATEINFO_FILES_PATH"updateinfo_01.xml"
 #define TEST_UPDATEINFO_02      TEST_UPDATEINFO_FILES_PATH"updateinfo_02.xml.xz"
+#define TEST_UPDATEINFO_03      TEST_UPDATEINFO_FILES_PATH"updateinfo_03.xml"
 
 #endif
diff --git a/tests/python/tests/test_updatecollection.py b/tests/python/tests/test_updatecollection.py
index f3433c0..71ac7dd 100644
--- a/tests/python/tests/test_updatecollection.py
+++ b/tests/python/tests/test_updatecollection.py
@@ -16,6 +16,13 @@ class TestCaseUpdateCollection(unittest.TestCase):
         self.assertEqual(col.name, None)
         self.assertEqual(col.packages, [])
 
+        module = cr.UpdateCollectionModule()
+        module.name = "kangaroo"
+        module.stream = "0"
+        module.version = 20180730223407
+        module.context = "deadbeef"
+        module.arch = "noarch"
+
         pkg = cr.UpdateCollectionPackage()
         pkg.name = "foo"
         pkg.version = "1.2"
@@ -30,12 +37,21 @@ class TestCaseUpdateCollection(unittest.TestCase):
 
         col.shortname = "short name"
         col.name = "long name"
+        col.module = module
         col.append(pkg)
 
         self.assertEqual(col.shortname, "short name")
         self.assertEqual(col.name, "long name")
         self.assertEqual(len(col.packages), 1)
 
+        # Check if the appended module was appended properly
+        module = col.module
+        self.assertEqual(module.name, "kangaroo")
+        self.assertEqual(module.stream, "0")
+        self.assertEqual(module.version, 20180730223407)
+        self.assertEqual(module.context, "deadbeef")
+        self.assertEqual(module.arch, "noarch")
+
         # Also check if the appended package was appended properly
         pkg = col.packages[0]
         self.assertEqual(pkg.name, "foo")
diff --git a/tests/python/tests/test_updatecollectionmodule.py b/tests/python/tests/test_updatecollectionmodule.py
new file mode 100644
index 0000000..1e92b12
--- /dev/null
+++ b/tests/python/tests/test_updatecollectionmodule.py
@@ -0,0 +1,31 @@
+import unittest
+import shutil
+import tempfile
+import os.path
+import createrepo_c as cr
+
+from .fixtures import *
+
+class TestCaseUpdateCollectionModule(unittest.TestCase):
+
+    def test_updatecollectionmodule_setters(self):
+        module = cr.UpdateCollectionModule()
+        self.assertTrue(module)
+
+        self.assertEqual(module.name, None)
+        self.assertEqual(module.stream, None)
+        self.assertEqual(module.version, 0)
+        self.assertEqual(module.context, None)
+        self.assertEqual(module.arch, None)
+
+        module.name = "foo"
+        module.stream = "0"
+        module.version = 20180730223407
+        module.context = "deadbeef"
+        module.arch = "noarch"
+
+        self.assertEqual(module.name, "foo")
+        self.assertEqual(module.stream, "0")
+        self.assertEqual(module.version, 20180730223407)
+        self.assertEqual(module.context, "deadbeef")
+        self.assertEqual(module.arch, "noarch")
diff --git a/tests/python/tests/test_updateinfo.py b/tests/python/tests/test_updateinfo.py
index f3b88e1..727b707 100644
--- a/tests/python/tests/test_updateinfo.py
+++ b/tests/python/tests/test_updateinfo.py
@@ -123,6 +123,100 @@ class TestCaseUpdateInfo(unittest.TestCase):
         now = datetime(now.year, now.month, now.day, now.hour, now.minute,
                        now.second, 0)
 
+        mod = cr.UpdateCollectionModule()
+        mod.name = "kangaroo"
+        mod.stream = "0"
+        mod.version = 18446744073709551615
+        mod.context = "deadbeef"
+        mod.arch = "x86"
+
+        pkg = cr.UpdateCollectionPackage()
+        pkg.name = "foo"
+        pkg.version = "1.2"
+        pkg.release = "3"
+        pkg.epoch = "0"
+        pkg.arch = "x86"
+        pkg.src = "foo.src.rpm"
+        pkg.filename = "foo.rpm"
+        pkg.sum = "abcdef"
+        pkg.sum_type = cr.SHA1
+        pkg.reboot_suggested = True
+
+        col = cr.UpdateCollection()
+        col.shortname = "short name"
+        col.name = "long name"
+        col.module = mod
+        col.append(pkg)
+
+        ref = cr.UpdateReference()
+        ref.href = "href"
+        ref.id = "id"
+        ref.type = "type"
+        ref.title = "title"
+
+        rec = cr.UpdateRecord()
+        rec.fromstr = "from"
+        rec.status = "status"
+        rec.type = "type"
+        rec.version = "version"
+        rec.id = "id"
+        rec.title = "title"
+        rec.issued_date = now
+        rec.updated_date = now
+        rec.rights = "rights"
+        rec.release = "release"
+        rec.pushcount = "pushcount"
+        rec.severity = "severity"
+        rec.summary = "summary"
+        rec.description = "description"
+        rec.solution = "solution"
+        rec.append_collection(col)
+        rec.append_reference(ref)
+
+        ui = cr.UpdateInfo()
+        ui.append(rec)
+
+        xml = ui.xml_dump()
+
+        self.assertEqual(xml,
+"""<?xml version="1.0" encoding="UTF-8"?>
+<updates>
+  <update from="from" status="status" type="type" version="version">
+    <id>id</id>
+    <title>title</title>
+    <issued date="%(now)s"/>
+    <updated date="%(now)s"/>
+    <rights>rights</rights>
+    <release>release</release>
+    <pushcount>pushcount</pushcount>
+    <severity>severity</severity>
+    <summary>summary</summary>
+    <description>description</description>
+    <solution>solution</solution>
+    <references>
+      <reference href="href" id="id" type="type" title="title"/>
+    </references>
+    <pkglist>
+      <collection short="short name">
+        <name>long name</name>
+        <module name="kangaroo" stream="0" version="18446744073709551615" context="deadbeef" arch="x86"/>
+        <package name="foo" version="1.2" release="3" epoch="0" arch="x86" src="foo.src.rpm">
+          <filename>foo.rpm</filename>
+          <sum type="sha1">abcdef</sum>
+          <reboot_suggested/>
+        </package>
+      </collection>
+    </pkglist>
+  </update>
+</updates>
+""" % {"now": now.strftime("%Y-%m-%d %H:%M:%S")})
+
+    def test_updateinfo_xml_dump_04(self):
+        now = datetime.now()
+        # Microseconds are always 0 in updateinfo
+        now = datetime(now.year, now.month, now.day, now.hour, now.minute,
+                       now.second, 0)
+
         pkg = cr.UpdateCollectionPackage()
         pkg.name = "foo"
         pkg.version = "1.2"
@@ -135,6 +229,7 @@ class TestCaseUpdateInfo(unittest.TestCase):
         pkg.sum_type = cr.SHA1
         pkg.reboot_suggested = True
 
+        # Collection without module
         col = cr.UpdateCollection()
         col.shortname = "short name"
         col.name = "long name"
@@ -167,6 +262,99 @@ class TestCaseUpdateInfo(unittest.TestCase):
 
         ui = cr.UpdateInfo()
         ui.append(rec)
+
+        xml = ui.xml_dump()
+
+        self.assertEqual(xml,
+"""<?xml version="1.0" encoding="UTF-8"?>
+<updates>
+  <update from="from" status="status" type="type" version="version">
+    <id>id</id>
+    <title>title</title>
+    <issued date="%(now)s"/>
+    <updated date="%(now)s"/>
+    <rights>rights</rights>
+    <release>release</release>
+    <pushcount>pushcount</pushcount>
+    <severity>severity</severity>
+    <summary>summary</summary>
+    <description>description</description>
+    <solution>solution</solution>
+    <references>
+      <reference href="href" id="id" type="type" title="title"/>
+    </references>
+    <pkglist>
+      <collection short="short name">
+        <name>long name</name>
+        <package name="foo" version="1.2" release="3" epoch="0" arch="x86" src="foo.src.rpm">
+          <filename>foo.rpm</filename>
+          <sum type="sha1">abcdef</sum>
+          <reboot_suggested/>
+        </package>
+      </collection>
+    </pkglist>
+  </update>
+</updates>
+""" % {"now": now.strftime("%Y-%m-%d %H:%M:%S")})
+
+    def test_updateinfo_xml_dump_05(self):
+        now = datetime.now()
+        # Microseconds are always 0 in updateinfo
+        now = datetime(now.year, now.month, now.day, now.hour, now.minute,
+                       now.second, 0)
+
+        # Collection module with unset fields
+        mod = cr.UpdateCollectionModule()
+        mod.version = 18446744073709551615
+        mod.context = "deadbeef"
+        mod.arch = "x86"
+
+        pkg = cr.UpdateCollectionPackage()
+        pkg.name = "foo"
+        pkg.version = "1.2"
+        pkg.release = "3"
+        pkg.epoch = "0"
+        pkg.arch = "x86"
+        pkg.src = "foo.src.rpm"
+        pkg.filename = "foo.rpm"
+        pkg.sum = "abcdef"
+        pkg.sum_type = cr.SHA1
+        pkg.reboot_suggested = True
+
+        col = cr.UpdateCollection()
+        col.shortname = "short name"
+        col.name = "long name"
+        col.module = mod
+        col.append(pkg)
+
+        ref = cr.UpdateReference()
+        ref.href = "href"
+        ref.id = "id"
+        ref.type = "type"
+        ref.title = "title"
+
+        rec = cr.UpdateRecord()
+        rec.fromstr = "from"
+        rec.status = "status"
+        rec.type = "type"
+        rec.version = "version"
+        rec.id = "id"
+        rec.title = "title"
+        rec.issued_date = now
+        rec.updated_date = now
+        rec.rights = "rights"
+        rec.release = "release"
+        rec.pushcount = "pushcount"
+        rec.severity = "severity"
+        rec.summary = "summary"
+        rec.description = "description"
+        rec.solution = "solution"
+        rec.append_collection(col)
+        rec.append_reference(ref)
+
+        ui = cr.UpdateInfo()
+        ui.append(rec)
+
         xml = ui.xml_dump()
 
         self.assertEqual(xml,
@@ -190,6 +378,7 @@ class TestCaseUpdateInfo(unittest.TestCase):
     <pkglist>
       <collection short="short name">
         <name>long name</name>
+        <module version="18446744073709551615" context="deadbeef" arch="x86"/>
         <package name="foo" version="1.2" release="3" epoch="0" arch="x86" src="foo.src.rpm">
           <filename>foo.rpm</filename>
           <sum type="sha1">abcdef</sum>
diff --git a/tests/test_xml_parser_updateinfo.c b/tests/test_xml_parser_updateinfo.c
index 94768ce..3f0cfee 100644
--- a/tests/test_xml_parser_updateinfo.c
+++ b/tests/test_xml_parser_updateinfo.c
@@ -168,6 +168,90 @@ test_cr_xml_parse_updateinfo_02(void)
     cr_updateinfo_free(ui);
 }
 
+//Test for module support
+static void
+test_cr_xml_parse_updateinfo_03(void)
+{
+    GError *tmp_err = NULL;
+    cr_UpdateInfo *ui = cr_updateinfo_new();
+    cr_UpdateRecord *update;
+    cr_UpdateReference *ref;
+    cr_UpdateCollection *col;
+    cr_UpdateCollectionModule *module;
+    cr_UpdateCollectionPackage *pkg;
+
+    int ret = cr_xml_parse_updateinfo(TEST_UPDATEINFO_03, ui,
+                                      NULL, NULL, &tmp_err);
+
+    g_assert(tmp_err == NULL);
+    g_assert_cmpint(ret, ==, CRE_OK);
+
+    g_assert_cmpint(g_slist_length(ui->updates), ==, 6);
+    update = g_slist_nth_data(ui->updates, 3);
+
+    g_assert_cmpstr(update->from, ==, "errata@redhat.com");
+    g_assert_cmpstr(update->status, ==, "stable");
+    g_assert_cmpstr(update->type, ==, "enhancement");
+    g_assert_cmpstr(update->version, ==, "1");
+    g_assert_cmpstr(update->id, ==, "RHEA-2012:0058");
+    g_assert_cmpstr(update->title, ==, "Gorilla_Erratum");
+    g_assert_cmpstr(update->description, ==, "Gorilla_Erratum");
+
+    update = g_slist_nth_data(ui->updates, 4);
+
+    g_assert_cmpstr(update->id, ==, "RHEA-2012:0059");
+    g_assert_cmpstr(update->title, ==, "Duck_Kangaroo_Erratum");
+    g_assert_cmpstr(update->description, ==, "Duck_Kangaro_Erratum description");
+    g_assert_cmpstr(update->issued_date, ==, "2018-01-27 16:08:09");
+    g_assert_cmpstr(update->updated_date, ==, "2018-07-20 06:00:01 UTC");
+    g_assert_cmpstr(update->release, ==, "1");
+
+    g_assert_cmpint(g_slist_length(update->references), ==, 0);
+
+    g_assert_cmpint(g_slist_length(update->collections), ==, 2);
+    col = g_slist_nth_data(update->collections, 0);
+    g_assert_cmpstr(col->shortname, ==, "");
+    g_assert_cmpstr(col->name, ==, "coll_name1");
+
+    module = col->module;
+    g_assert_cmpstr(module->name, ==, "kangaroo");
+    g_assert_cmpstr(module->stream, ==, "0");
+    g_assert_cmpuint(module->version, ==, 20180730223407);
+    g_assert_cmpstr(module->context, ==, "deadbeef");
+    g_assert_cmpstr(module->arch, ==, "noarch");
+
+    g_assert_cmpint(g_slist_length(col->packages), ==, 1);
+    pkg = col->packages->data;
+    g_assert_cmpstr(pkg->name, ==, "kangaroo");
+    g_assert_cmpstr(pkg->version, ==, "0.3");
+    g_assert_cmpstr(pkg->release, ==, "1");
+    g_assert(!pkg->epoch);
+    g_assert_cmpstr(pkg->arch, ==, "noarch");
+    g_assert_cmpstr(pkg->src, ==, "http://www.fedoraproject.org");
+    g_assert_cmpstr(pkg->filename, ==, "kangaroo-0.3-1.noarch.rpm");
+    g_assert(!pkg->sum);
+    g_assert(!pkg->sum_type);
+
+    col = g_slist_nth_data(update->collections, 1);
+    g_assert_cmpstr(col->shortname, ==, "");
+    g_assert_cmpstr(col->name, ==, "coll_name2");
+
+    module = col->module;
+    g_assert_cmpstr(module->name, ==, "duck");
+    g_assert_cmpstr(module->stream, ==, "0");
+    g_assert_cmpuint(module->version, ==, 20180730233102);
+    g_assert_cmpstr(module->context, ==, "deadbeef");
+    g_assert_cmpstr(module->arch, ==, "noarch");
+
+    g_assert_cmpint(g_slist_length(col->packages), ==, 1);
+    pkg = col->packages->data;
+    g_assert_cmpstr(pkg->name, ==, "duck");
+    g_assert_cmpstr(pkg->version, ==, "0.7");
+    g_assert_cmpstr(pkg->filename, ==, "duck-0.7-1.noarch.rpm");
+
+    cr_updateinfo_free(ui);
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -179,6 +263,8 @@ main(int argc, char *argv[])
                     test_cr_xml_parse_updateinfo_01);
     g_test_add_func("/xml_parser_updateinfo/test_cr_xml_parse_updateinfo_02",
                     test_cr_xml_parse_updateinfo_02);
+    g_test_add_func("/xml_parser_updateinfo/test_cr_xml_parse_updateinfo_03",
+                    test_cr_xml_parse_updateinfo_03);
 
     return g_test_run();
 }
diff --git a/tests/testdata/updateinfo_files/updateinfo_03.xml b/tests/testdata/updateinfo_files/updateinfo_03.xml
new file mode 100644
index 0000000..ddbd99b
--- /dev/null
+++ b/tests/testdata/updateinfo_files/updateinfo_03.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<updates>
+<update from="errata@redhat.com" status="stable" type="security" version="1">
+  <id>RHEA-2012:0055</id>
+  <title>Sea_Erratum</title>
+  <release>1</release>
+  <issued date="2012-01-27 16:08:06"/>
+  <updated date="2012-01-27 16:08:06"/>
+  <description>Sea_Erratum</description>
+  <pkglist>
+    <collection short="">
+      <name>1</name>
+      <package arch="noarch" name="walrus" release="1" src="http://www.fedoraproject.org" version="5.21">
+        <filename>walrus-5.21-1.noarch.rpm</filename>
+      </package>
+      <package arch="noarch" name="penguin" release="1" src="http://www.fedoraproject.org" version="0.9.1">
+        <filename>penguin-0.9.1-1.noarch.rpm</filename>
+      </package>
+      <package arch="noarch" name="shark" release="1" src="http://www.fedoraproject.org" version="0.1">
+        <filename>shark-0.1-1.noarch.rpm</filename>
+      </package>
+    </collection>
+  </pkglist>
+</update>
+
+<update from="errata@redhat.com" status="stable" type="security" version="1">
+  <id>RHEA-2012:0056</id>
+  <title>Bird_Erratum</title>
+  <release>1</release>
+  <issued date="2013-01-27 16:08:08"/>
+  <updated date="2013-02-27 17:00:00"/>
+  <description>ParthaBird_Erratum</description>
+  <pkglist>
+    <collection short="">
+      <name>1</name>
+      <package arch="noarch" name="crow" release="1" src="http://www.fedoraproject.org" version="0.8">
+        <filename>crow-0.8-1.noarch.rpm</filename>
+      </package>
+      <package arch="noarch" name="stork" release="2" src="http://www.fedoraproject.org" version="0.12">
+        <filename>stork-0.12-2.noarch.rpm</filename>
+      </package>
+      <package arch="noarch" name="duck" release="1" src="http://www.fedoraproject.org" version="0.6">
+        <filename>duck-0.6-1.noarch.rpm</filename>
+      </package>
+    </collection>
+  </pkglist>
+</update>
+
+<update from="errata@redhat.com" status="stable" type="security" version="1">
+  <id>RHEA-2012:0057</id>
+  <title>Bear_ErratumPARTHA</title>
+  <release>1</release>
+  <issued date="2013-01-27 16:08:05"/>
+  <updated date="2013-01-27 16:08:05 UTC"/>
+  <description>Bear_Erratum</description>
+  <pkglist>
+    <collection short="">
+      <name>1</name>
+      <package arch="noarch" name="bear" release="1" src="http://www.fedoraproject.org" version="4.1">
+        <filename>bear-4.1-1.noarch.rpm</filename>
+      </package>
+    </collection>
+  </pkglist>
+</update>
+
+<update from="errata@redhat.com" status="stable" type="enhancement" version="1">
+  <id>RHEA-2012:0058</id>
+  <title>Gorilla_Erratum</title>
+  <release>1</release>
+  <issued date="2013-01-27 16:08:09"/>
+  <updated date="2014-07-20 06:00:01 UTC"/>
+  <description>Gorilla_Erratum</description>
+  <pkglist>
+    <collection short="">
+      <name>1</name>
+      <package arch="noarch" name="gorilla" release="1" src="http://www.fedoraproject.org" version="0.62">
+        <filename>gorilla-0.62-1.noarch.rpm</filename>
+      </package>
+    </collection>
+  </pkglist>
+</update>
+
+<update from="errata@redhat.com" status="stable" type="enhancement" version="1">
+  <id>RHEA-2012:0059</id>
+  <title>Duck_Kangaroo_Erratum</title>
+  <release>1</release>
+  <issued date="2018-01-27 16:08:09"/>
+  <updated date="2018-07-20 06:00:01 UTC"/>
+  <description>Duck_Kangaro_Erratum description</description>
+  <pkglist>
+    <collection short="">
+      <name>coll_name1</name>
+      <module name="kangaroo" stream="0" version="20180730223407" context="deadbeef" arch="noarch"/>
+      <package arch="noarch" name="kangaroo" release="1" src="http://www.fedoraproject.org"
+               version="0.3">
+        <filename>kangaroo-0.3-1.noarch.rpm</filename>
+      </package>
+    </collection>
+    <collection short="">
+      <name>coll_name2</name>
+      <module name="duck" stream="0" version="20180730233102" context="deadbeef" arch="noarch"/>
+      <package arch="noarch" name="duck" release="1" src="http://www.fedoraproject.org"
+               version="0.7">
+        <filename>duck-0.7-1.noarch.rpm</filename>
+      </package>
+    </collection>
+  </pkglist>
+</update>
+
+<update from="errata@redhat.com" status="stable" type="enhancement" version="1">
+  <id>RHEA-2012:0060</id>
+  <title>Duck_0.8_Erratum</title>
+  <release>1</release>
+  <issued date="2018-01-29 16:08:09"/>
+  <updated date="2018-07-29 06:00:01 UTC"/>
+  <description>Duck_0.8_Erratum description</description>
+  <pkglist>
+    <collection short="">
+      <name>coll_name</name>
+      <module name="duck" stream="0" version="201809302113907" context="deadbeef" arch="noarch"/>
+      <package arch="noarch" name="duck" release="1" src="http://www.fedoraproject.org"
+               version="0.8">
+        <filename>duck-0.8-1.noarch.rpm</filename>
+      </package>
+    </collection>
+  </pkglist>
+</update>
+</updates>
--
libgit2 0.27.8