diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f094e84 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000..7509716 --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,68 @@ +name: Build and upload to PyPI + +on: + workflow_dispatch: + pull_request: + push: + tags: + - v* + release: + types: + - published + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, macos-11] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.2 + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/pyrpmdb + permissions: + id-token: write + # if: github.event_name == 'release' && github.event.action == 'published' + # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v3 + with: + # unpacks default artifact into dist/ + # if `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # To test: repository-url: https://test.pypi.org/legacy/ + skip-existing: true + verbose: true + verify-metadata: true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..adba7e4 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,16 @@ + +This package uses [setuptools-golang](https://pypi.org/project/setuptools-golang-examples/) for installation and to build manylinux wheels. +The code is modified [this line](https://github.com/asottile/setuptools-golang/blob/main/setuptools_golang.py#L234) needs adjusting we replace docker image with a more modern version to get support for 3.10 and 3.11. + +Currently using quay.io/pypa/manylinux_2_24_x86_64:latest as latest image this gets us 3.6 - 3.11 on linu + + +Build is currently +```shell +setuptools-golang-build-manylinux-wheels --golang=1.21.1 --pythons 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311' +cd src/pygobuildinfo +env GOOS="windows" GOARCH="amd64" CGO_ENABLED="1" CC="x86_64-w64-mingw32-gcc" go build -buildmode=c-shared -o pygo.dll +``` +So builds for linux x86 images for python versions 3.6 through to 3.10 + +NB On environments that prebuilt libraries are not supported go >= 1.8 MUST be installed and on the path. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f072a02 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include *.go go.mod go.sum +include LICENSE \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..682674a --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +A python package that extracts go build information from go based executables, go.mod and go.sum files and shared libraries. The package leverages the golang debug/buildinfo and golang.org/x/mod/modfile packages to extract the information hence relies on a shared library to do this work. + +Example usage + +```python + +from pyrpmdb import get_rpm_db_info +import json + + +def test_get_info(file): + res = get_rpm_db_info(file) + print(json.dumps(res, indent=4)) + + +test_get_info("foo/bar") +test_get_info("/usr/bin/du") +test_get_info("/Users/auser/go/src/spire/support/oidc-discovery-provider/oidc-discovery-provider.elf") +test_get_info("/Users/auser/go/src/spire/support/oidc-discovery-provider/oidc-discovery-provider.exe") +test_get_info("/Users/auser/go/src/spire/support/oidc-discovery-provider/oidc-discovery-provider") +test_get_info("/Users/auser/go/pygobuildinfo/pybuildInfo/_pyGoBuildinfo.cpython-39-darwin.so") +test_go_mod("/Users/auser/go/src/pygobuildInfo/go.mod") +test_go_sum("/Users/auser/go/src/pygobuildInfo/go.sum") +``` + +The result returned is always a dict object for errors the dictionary returned contains a key; +"error" like; +```python +{ + "error": "path error:foo/bar" +} +``` +or +```python +{ + "error": "/usr/bin/du: could not read Go build info from /usr/bin/du: unrecognized file format" +} +``` +on success a python list of rpm package info struct is returned of this go structure serialized + +```go +type PackageInfo struct { + Epoch *int + Name string + Version string + Release string + Arch string + SourceRpm string + Size int + License string + Vendor string + Modularitylabel string + Summary string + PGP string + SigMD5 string + DigestAlgorithm DigestAlgorithm + InstallTime int + BaseNames []string + DirIndexes []int32 + DirNames []string + FileSizes []int32 + FileDigests []string + FileModes []uint16 + FileFlags []int32 + UserNames []string + GroupNames []string + + Provides []string + Requires []string +} +``` +```python +[ + { + "Name": "package_name", + "Version": "version" + } +] +``` +This spackage relies on a shared go library that leverages https://pkg.go.dev/github.com/knqyf263/go-rpmdb/pkg + +So relies on this for database support. + diff --git a/get_rpm_info b/get_rpm_info new file mode 100644 index 0000000..63b0ad3 --- /dev/null +++ b/get_rpm_info @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from pyrpmdb import get_rpm_db_info +import glob +import sys +import json + +for arg in sys.argv[1:]: + for file in glob.glob(arg): + result = get_rpm_db_info(file) + print(json.dumps(result,indent=4)) \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..67eda65 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module pyrpmdb + +go 1.21 + +require github.com/knqyf263/go-rpmdb v0.0.0-20201215100354-a9e3110d8ee1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..71bc86a --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= +github.com/knqyf263/go-rpmdb v0.0.0-20201215100354-a9e3110d8ee1/go.mod h1:RDPNeIkU5NWXtt0OMEoILyxwUC/DyXeRtK295wpqSi0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/installGo.ps1 b/installGo.ps1 new file mode 100644 index 0000000..e32b384 --- /dev/null +++ b/installGo.ps1 @@ -0,0 +1,88 @@ +# Install the current Go release +param( + [string]$w='c:\go', + [string]$v='1.21.1' +) +Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser +$OutputVariable = (go version) | Out-String +if ($?) +{ + Write-Host 'Go Installed Already ...' $OutputVariable; + exit 0 +} +$ErrorActionPreference = 'Stop'; +$ProgressPreference = 'SilentlyContinue'; +$env:GOPATH= 'C:\go' +$newPath = ('{0}\bin;C:\Program Files\Go\bin;{1}' -f $env:GOPATH, $env:PATH); +Write-Host ('Updating PATH: {0}' -f $newPath); +[Environment]::SetEnvironmentVariable('PATH', $newPath, [EnvironmentVariableTarget]::User); +$url = 'https://dl.google.com/go/go1.21.1.windows-amd64.zip'; +Write-Host ('Downloading {0} ...' -f $url); +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; +Invoke-WebRequest -Uri $url -OutFile 'go.zip'; +Write-Host 'Expanding ...'; +Expand-Archive go.zip -DestinationPath C:\; +Write-Host 'Moving ...'; +Move-Item -Path C:\go -Destination 'C:\Program Files\Go'; +Write-Host 'Removing ...'; +Remove-Item go.zip -Force; +Write-Host 'Verifying install ("go version") ...'; +go version; +Write-Host 'Complete.'; +# # Wed, 13 Sep 2023 01:39:01 GMT +# WORKDIR C:\go +# +# # installer file +# $file = 'go' + $v + '.windows-amd64.msi' +# +# # set defaults +# $workDir = 'Documents\go' +# $url = 'https://dl.google.com/go/' + $file +# $dest = Join-Path $Home "Downloads" +# $dest = Join-Path $dest $file +# +# # if $w wasn't passed; use the default +# if ($w -eq "") { +# $gopath = Join-Path $Home $workDir +# } else { +# $gopath = $w +# } +# +# # Setup the Go workspace; if it doesn't exist. +# If (!(Test-Path $gopath)) { +# New-Item -path $gopath -type directory +# } +# +# # Create GOPATH and set PATH to use $GOPATH\bin +# $gopathbin = Join-Path $gopath "bin" +# #$gopathbin = ';' + $gopathbin +# +# # set the $GOPATH +# [Environment]::SetEnvironmentVariable( "GOPATH", $gopath, [System.EnvironmentVariableTarget]::User ) +# +# # see the $GOBIN +# [Environment]::SetEnvironmentVariable( "GOBIN", $gopathbin, [System.EnvironmentVariableTarget]::User ) +# +# Write-Output "downloading $url" +# # Create client, set its info, and download +# $wc = New-Object System.Net.WebClient +# $wc.UseDefaultCredentials = $true +# $wc.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f") +# $wc.DownloadFile($url, $dest) +# +# Write-Output "$url downloaded as $dest" +# Write-Output "installing $v..." +# # Run the msi +# Start-Process $dest +# +# Write-Output "done" + +# +# +# $url = 'https://dl.google.com/go/go1.21.1.windows-amd64.zip'; +# Write-Host ('Downloading {0} ...' -f $url); +# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; +# Invoke-WebRequest -Uri $url -OutFile 'go.zip'; +# Write-Host 'Expanding ...'; Expand-Archive go.zip -DestinationPath C:\\; +# Write-Host 'Moving ...'; +# Move-Item -Path C:\\go -Destination 'C:\\Program Files\\Go' \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2ab681f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.cibuildwheel] + + +[tool.cibuildwheel.windows] +archs=["AMD64"] +before-all = "powershell {project}\\installGo.ps1" +skip = "cp36-win*" +environment= """ +PATH="C:\\Go\\bin;C:\\Program Files\\Go\\bin;$PATH" +GOPATH="C:\\Go" +""" + +[tool.cibuildwheel.linux] +archs=["x86_64"] +before-all = "yum install -y golang" + +[[tool.cibuildwheel.overrides]] +select = "*-musllinux*" +before-all = "wget https://golang.org/dl/go1.21.1.linux-amd64.tar.gz;tar -C /usr/local -xzf go1.21.1.linux-amd64.tar.gz" +environment = "PATH=$PATH:/usr/local/go/bin" + + + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..946f76d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +[metadata] +name = pyrpmdb +version = 0.1.14 +author = Mike Moore +author_email = z_z_zebra@yahoo.com +license = MIT +description = A utility to extract rpm package information from rpm database +url = https://github.com/Mikemoore63/pyrpmdb +long_description = file: README.md +long_description_content_type = text/markdown +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: OS Independent + +[options] +py_modules = pyrpmdb +zip_safe = False +setup_requires = setuptools-golang +scripts = get_rpm_info +package_dir= + =src +packages=find: + +[options.packages.find] +where=src + +[options.entry_points] +pyinstaller40 = + hook-dirs = pyrpmdb._pyinstaller:get_hook_dirs + tests = pyrpmdb._pyinstaller:get_PyInstaller_tests diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..75633f9 --- /dev/null +++ b/setup.py @@ -0,0 +1,9 @@ + +from setuptools import Extension, setup +import os + +if __name__ == '__main__': + setup( + build_golang={'root': 'github.com/MikeMoore63/pyrpmdb'}, + ext_modules=[Extension('pyrpmdb._pyrpmdb', ['src/pyrpmdb/pyrpmdb.go'])] + ) diff --git a/src/pyrpmdb/__init__.py b/src/pyrpmdb/__init__.py new file mode 100644 index 0000000..026b6b4 --- /dev/null +++ b/src/pyrpmdb/__init__.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import + +from pyrpmdb._pyinstaller import get_hook_dirs, get_PyInstaller_tests +__all__ = [ + "get_hook_dirs", + "get_PyInstaller_tests", + "get_rpm_db_info" +] + +"""Extract buildinfo from go built binaries""" + +import ctypes +import json +import os +from distutils.sysconfig import get_config_var +from pathlib import Path + + +# Location of shared library +here = Path(__file__).absolute().parent +ext_suffix = get_config_var('EXT_SUFFIX') +so_file = os.path.join(here, ('_pyrpmdb' + ext_suffix)) + +# Load functions from shared library set their signatures +so = ctypes.cdll.LoadLibrary(so_file) +get_get_rpm_db_info_so = so.getrpmdbInfo +get_get_rpm_db_info_so.argtypes = [ctypes.c_char_p] +get_get_rpm_db_info_so.restype = ctypes.c_void_p +free = so.free +free.argtypes = [ctypes.c_void_p] + + +def get_rpm_db_info(file_name): + """Check (in parallel) digital signature of all files in root_dir. + We assume there's a sha1sum.txt file under root_dir + """ + res = get_get_rpm_db_info_so(file_name.encode('utf-8')) + if res is not None: + result = {"error": "Error converting result to json"} + try: + result = json.loads(ctypes.string_at(res).decode('utf-8')) + except json.JSONDecodeError as e: + pass + finally: + free(res) + return (result) + diff --git a/src/pyrpmdb/_pyinstaller/__init__.py b/src/pyrpmdb/_pyinstaller/__init__.py new file mode 100644 index 0000000..e8c4c1f --- /dev/null +++ b/src/pyrpmdb/_pyinstaller/__init__.py @@ -0,0 +1,34 @@ +# This package provides a hook for PyInstaller +# needed to successfully freeze +# the :doc:`pyi_hooksample package <../__init__.py>`. +# It also provides test-data for that hook. +import os + +# Functions +# ========= +# +# .. _get_hook_dirs: +# +# get_hook_dirs +# ------------- +# +# Tell PyInstaller where to find hooks provided by this distribution; +# this is referenced by the :ref:`hook registration `. +# This function returns a list containing only the path to this +# directory, which is the location of these hooks. + +def get_hook_dirs(): + return [os.path.dirname(__file__)] + +# .. _get_PyInstaller_tests: +# +# get_PyInstaller_tests +# --------------------- +# +# Tell PyInstaller where to find test-data of the hooks provided by this +# distribution; this is referenced by the :ref:`test-data registration +# `. This function returns a list containing only +# the path to this directory, which is the location of these test-data. + +def get_PyInstaller_tests(): + return [os.path.dirname(__file__)] \ No newline at end of file diff --git a/src/pyrpmdb/_pyinstaller/hook-pyrpmdb.py b/src/pyrpmdb/_pyinstaller/hook-pyrpmdb.py new file mode 100644 index 0000000..024a742 --- /dev/null +++ b/src/pyrpmdb/_pyinstaller/hook-pyrpmdb.py @@ -0,0 +1,11 @@ +from PyInstaller.utils.hooks import collect_dynamic_libs +from distutils.sysconfig import get_config_var +from pathlib import Path +import os + +here = Path(__file__).absolute().parent.parent +print(here) +ext_suffix = get_config_var('EXT_SUFFIX') +so_file = os.path.join(here, ('_ppyrpmdb' + ext_suffix)) + +binaries = [(so_file,'.')] diff --git a/src/pyrpmdb/pyrpmdb.go b/src/pyrpmdb/pyrpmdb.go new file mode 100644 index 0000000..91eb2e8 --- /dev/null +++ b/src/pyrpmdb/pyrpmdb.go @@ -0,0 +1,52 @@ +package main + +import ( + "C" + "encoding/json" + "errors" + "fmt" + rpmdb "github.com/knqyf263/go-rpmdb/pkg" + "os" + "path/filepath" +) + +//export getrpmdbInfo +func getrpmdbInfo(fileNameIn *C.char) *C.char { + return C.CString(getrpmdbInfodInfoInternal(C.GoString(fileNameIn))) +} + +func main() { + //getgobuildinfo(C.CString("foo/bar")) + //getgobuildinfo(C.CString("/usr/bin/du")) + //getgobuildinfo(C.CString("/Users/mike/go/pygobuildinfo/spire/support/oidc-discovery-provider/oidc-discovery-provider.elf")) + //getgobuildinfo(C.CString("/Users/mike/go/pygobuildinfo/spire/support/oidc-discovery-provider/oidc-discovery-provider.exe")) + //getgobuildinfo(C.CString("/Users/mike/go/pygobuildinfo/spire/support/oidc-discovery-provider/oidc-discovery-provider")) +} + +func getrpmdbInfodInfoInternal(fileName string) string { + returnValue := "{ \"error\" : \"Unknown\" }" + db, err := rpmdb.Open(fileName) + if err != nil { + if pathErr := (*os.PathError)(nil); errors.As(err, &pathErr) && filepath.Clean(pathErr.Path) == filepath.Clean(fileName) { + returnValue = fmt.Sprintf("{ \"error\": \"path error:%v\" }", fileName) + } else { + returnValue = fmt.Sprintf("{ \"error\": \"%s: %v\"}", fileName, err) + } + } else { + pkgList, err := db.ListPackages() + if err != nil { + returnValue = fmt.Sprintf("{ \"error\": \"%s: %v\"}", fileName, err) + } else { + mySlice := []rpmdb.PackageInfo{} + for _, pkg := range pkgList { + mySlice = append( + mySlice, + *pkg) + } + data, _ := json.Marshal(mySlice) + returnValue = string(data) + } + } + // fmt.Printf("%s\n", returnValue) + return returnValue +} diff --git a/test-data/cbl-mariner-2.0-rpmdb.sqlite b/test-data/cbl-mariner-2.0-rpmdb.sqlite new file mode 100644 index 0000000..e0ac2e8 Binary files /dev/null and b/test-data/cbl-mariner-2.0-rpmdb.sqlite differ diff --git a/test-data/centos5-plain-Packages b/test-data/centos5-plain-Packages new file mode 100644 index 0000000..759a88f Binary files /dev/null and b/test-data/centos5-plain-Packages differ diff --git a/test-data/centos6-devtools-Packages b/test-data/centos6-devtools-Packages new file mode 100644 index 0000000..c8951aa Binary files /dev/null and b/test-data/centos6-devtools-Packages differ diff --git a/test-data/centos7-devtools-Packages b/test-data/centos7-devtools-Packages new file mode 100644 index 0000000..fee5042 Binary files /dev/null and b/test-data/centos7-devtools-Packages differ diff --git a/test-data/centos7-httpd24-Packages b/test-data/centos7-httpd24-Packages new file mode 100644 index 0000000..8de7555 Binary files /dev/null and b/test-data/centos7-httpd24-Packages differ diff --git a/test-data/fedora35-plus-mongo-rpmdb.sqlite b/test-data/fedora35-plus-mongo-rpmdb.sqlite new file mode 100644 index 0000000..e0ac2e8 Binary files /dev/null and b/test-data/fedora35-plus-mongo-rpmdb.sqlite differ