Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/rpm-software-management/createrepo_c.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleš Matěj <amatej@redhat.com>2019-01-08 17:44:55 +0300
committeramatej <matej.ales@seznam.cz>2019-06-19 11:17:21 +0300
commita48db44b73785b5d5fbe8ae827522695fa0fd9ce (patch)
tree5bd55f9df6d523fb909485a13d0b2541e541ce40
parentdfe7218f07ffa70b73c51c71b0f051be926b6d92 (diff)
Add support for modular errata (RhBug:1656584)
-rw-r--r--src/python/CMakeLists.txt1
-rw-r--r--src/python/__init__.py3
-rw-r--r--src/python/createrepo_cmodule.c8
-rw-r--r--src/python/updatecollection-py.c43
-rw-r--r--src/python/updatecollectionmodule-py.c274
-rw-r--r--src/python/updatecollectionmodule-py.h33
-rw-r--r--src/updateinfo.c44
-rw-r--r--src/updateinfo.h24
-rw-r--r--src/xml_dump_updateinfo.c20
-rw-r--r--src/xml_parser_internal.h2
-rw-r--r--src/xml_parser_updateinfo.c54
-rw-r--r--tests/fixtures.h1
-rw-r--r--tests/python/tests/test_updatecollection.py16
-rw-r--r--tests/python/tests/test_updatecollectionmodule.py31
-rw-r--r--tests/python/tests/test_updateinfo.py189
-rw-r--r--tests/test_xml_parser_updateinfo.c86
-rw-r--r--tests/testdata/updateinfo_files/updateinfo_03.xml128
17 files changed, 957 insertions, 0 deletions
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>