Easy Way to Make an Rpm Out of a Python Script

This is the concluding post of the trilogy dedicated to how to set a well structured Python project, adult with professional style, suitable to be used inside the context of a Continuous Integration toolchain. This time nosotros focus on how to package all nosotros have done and then far every bit RPM packages, showing how to break down everything into subpackages that besides perform mail installation tasks.
If you haven't read the previous 2 postsouth, you must do it correct now since they are requisite to sympathize this post. In improver to that this post relies onto objects that are beingness created in the previous posts.

Read them in the following social club:

  1. Python Full Featured Project
  2. Python Setup Tools

The operating environment used in this post is Cherry Chapeau Enterprise Linux 8 or CentOS viii - using a different environment may lead to having to adapt or even change things.

Bundle Management With RPM

The Redhat Package Ganager (RPM) is an open up packaging system adult by Red Hat Linux many, many years ago. As well beingness used past Blood-red Hat based or derived Linux distributions, it is used as well by other distributions such as Suse.

RPM stores data of the installed packages within a system-wide database (the RPM db) where a lot of metadata such as software version and dependencies are nerveless.
The database can be managed and queried using the rpmcommand line utility: it can be run by whatsoever user, but only the "root" user has write admission to the RPM database files.

This means that whatsoever user can query the RPM issuing commands like this:

          rpm -qi openssh-server        

output is as follows:

          Name : openssh-server Version : 8.0p1 Release : 6.el8_4.ii Compages: x86_64 Install Date: Thu 24 Jun 2021 06:31:12 PM UTC Grouping : Organisation Surroundings/Daemons Size : 1034496 License : BSD Signature : RSA/SHA256, Friday xi Jun 2021 12:29:28 AM UTC, Key ID 15af5dac6d745a60 Source RPM : openssh-8.0p1-6.el8_4.2.src.rpm Build Date : Friday 11 Jun 2021 12:16:02 AM UTC Build Host : ord1-prod-x86build003.svc.aws.rockylinux.org Relocations : (non relocatable) Packager : infrastructure@rockylinux.org Vendor : Rocky URL : http://world wide web.openssh.com/portable.html Summary : An open source SSH server daemon Description : OpenSSH is a free version of SSH (Secure Trounce), a program for logging into and executing commands on a remote car. This packet contains the secure vanquish daemon (sshd). The sshd daemon allows SSH clients to securely connect to your SSH server.                  

these are the information near an installed RPM package called "openssh-server": we requested to display this data past specifying that we want to query the RPM database ("-q" command switch) about information ("-i" command switch) on the already installed RPM package called "openssh-server".

It is straightforward that only root can update the RPM db:  the RPM db is updated only when packages get installed/updated/removed; root is the only user that is granted the right to write into system directory trees aimed at store software packages. Yous tin of form grant the right to utilize installation commands to other users leveraging on sudo by configuring sudo rules.

The Redhat Parcel Manager installs packages of RPM format: this kind of package is a sophisticated archive that does not just pack a set of files and directories inside a bundle file, but tin can too run scripts and evaluate conditionals.
It is made by putting a header construction on elevation of a CPIO archive.

The package itself is has 4 sections:

  • the file identifier (magic number) used to identify the file contents equally an RPM package
  • the signature to verify the integrity of the package
  • the header or "tagged" information containing bundle information, version numbers and copyright
  • the payload, that is the bodily CPIO archive containing the program files.

let'south download the "epel-release" RPM package to give it a closer expect:

          yumdownloader epel-release        

later on a while, "epel-release-viii-13.el8.noarch.rpm" RPM package gets downloaded to our organisation.

Since files provided by a RPM package are packaged within a CPIO archive, they can easily listed also without using the rpm command line utility itself as follows:

          rpm2cpio epel-release-8-13.el8.noarch.rpm | cpio -i --list        

output is every bit follows:

          ./etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8 ./etc/yum.repos.d/epel-modular.repo ./etc/yum.repos.d/epel-playground.repo ./etc/yum.repos.d/epel-testing-modular.repo ./etc/yum.repos.d/epel-testing.repo ./etc/yum.repos.d/epel.repo ./usr/lib/systemd/system-preset/90-epel.preset ./usr/share/doc/epel-release ./usr/share/doctor/epel-release/README-epel-8-packaging.md ./usr/share/licenses/epel-release ./usr/share/licenses/epel-release/GPL 74 blocks                  

RPM provides several benefits - an interesting one is showing information about what a parcel provides fifty-fifty earlier we install information technology:

          rpm -q --provides epel-release-eight-13.el8.noarch.rpm        

output is every bit follows:

          config(epel-release) = 8-13.el8 epel-release = 8-thirteen.el8                  

please notation how this time we queried a package that is not installed withal .

RPM tin can be queried too to know what a package requires:

          rpm -q --requires epel-release-8-xiii.el8.noarch.rpm        

output is as follows:

          config(epel-release) = viii-13.el8 redhat-release >= 8 rpmlib(CompressedFileNames) <= three.0.4-1 rpmlib(FileDigests) <= 4.half dozen.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 rpmlib(PayloadIsXz) <= 5.ii-1                  

RPM can exist queried also to know if a package does execute at install or uninstall anything and the statements that are run at that fourth dimension.

For example, we can query the already installed package "openssh-server" equally follows:

          rpm -q --scripts openssh-server        

the output is as follows:

          preinstall scriptlet (using /bin/sh): getent group sshd >/dev/null || groupadd -m 74 -r sshd || : getent passwd sshd >/dev/zippo || \   useradd -c "Privilege-separated SSH" -u 74 -one thousand sshd \   -s /sbin/nologin -r -d /var/empty/sshd sshd two> /dev/null || : postinstall scriptlet (using /bin/sh):  if [ $1 -eq 1 ] ; then          # Initial installation          systemctl --no-reload preset sshd.service sshd.socket &>/dev/naught || :  fi preuninstall scriptlet (using /bin/sh):  if [ $1 -eq 0 ] ; then          # Bundle removal, non upgrade          systemctl --no-reload disable --now sshd.service sshd.socket &>/dev/zip || :  fi postuninstall scriptlet (using /bin/sh):  if [ $1 -ge one ] ; then          # Package upgrade, not uninstall          systemctl endeavour-restart sshd.service &>/dev/zip || :  fi        
  • preinstall scriptlet is run before installing the RPM package contents
  • postinstall scriptlet is run later on installing the RPM packet contents
  • preuninstall scriptlet is run before uninstalling the RPM package
  • postuninstall scriptlet is run after uninstalling the RPM packet

These are of course only a few of the many benefits provided by RPM, so to allow you become the gist.

Packaging as RPM provides a lot of benefits: that's why existence skilled on it is valuable not only for developers just also for arrangement engineers.

Setup The RPM Evolution Environment

Install The Required Tools

RPMs are built past having the rpmbuild command line utility: it is provided by the rpmbuildRPM package. We can install it along with other handy tools such as "rpmdevtools" and "rpmlint" every bit follows:

          sudo dnf install -y rpm-build rpmdevtools rpmlint        

Generate the RPM Development Tree

The building of RPM packages happens within the context of the so called RPM development tree: this directory tree is on a per user footing and can exist hands generated equally follows:

          rpmdev-setuptree        

it creates the following directory tree:

          ~/rpmbuild/ ├── SOURCES ├── SPECS ├── BUILD ├── RPMS └── SRPMS                  

where:

  • SOURCES is the directory where athenaeum containing the source files must be put. Athenaeum tin be tar archives, either compressed or non.
  • SPECS is the directory that contains the ".spec" files: these are the files that contain all the necessary statements that describe the RPM package.
  • BUILD is the directory used during the build procedure, where temporary files are stored, created, moved etc.
  • RPMS is directory that holds the built RPM packages
  • SRPMS is the directory that holds the ".src.rpm" packages: these are RPM packages that provide the annal containing the source files along with the SPEC file, so providing everything is needed to rebuild the RPM package.

Never and e'er develop and/or build RPM packages every bit the root user.

It is non really necessary to generate the RPM development tree in the use case described in this mail: I'grand showing you this merely for the sake of completeness and and then to have the take chances to describe th purpose of each direcotry.

Packaging as RPM

Now that we know at least the basics of RPM, we tin see how to generate an RPM package that ships the Python package we created in the previous ii posts.

Generate a stub SPEC file

First and foremost allow'southward get back to the Python3 project nosotros developed in the previous two posts - alter directory to the root of the Carcano's "foolist" project:

          cd ~/fooproject        

and create the "RPM" and "RPM/SPECS" directories:

          mkdir RPM mkdir RPM/SPECS cd RPM/SPECS        

RPM are built by having the rpmbuild command line utility reading the SPEC file: we can easily generate a stub SPEC file as follows:

          rpmdev-newspec carcano_foolist        

the outcome is the "carcano_foolist.spec" file beneath "RPM/SPECS" directories: we use this stub and complete it with the missing settings that are necessary to package "carcano_foolist" as RPM.

Configure the SPEC file

We begin by editing "RPM/SPECS/carcano_foolist.spec" file by defining a few RPM macros at the very first two lines of the file:

          %{!?_version: %define _version 0.0.1 } %global srcname carcano_foolist                  
  • the first line is nearly the "_version" macro, used to set up the version of the built RPM packages: it tin exist explicitly set  using the "--ascertain" command line switch of rpmbuild, avoiding having to hard-lawmaking it within the SPEC file. The outset line checks if  macro is defined, and if it isn't defines it with "0.0.1" equally default value.
  • the second line defines the "srcname" macro with "carcano_foolist" as a value at the global scope.

Despite the character to be used to annotate-out lines used in SPEC files is '#', it does non piece of work like so with macros - to annotate-out a macro you must prepend an additional '%'. So for example, to comment on the second of the above lines, information technology must expect like "%%global srcname carcano_foolist".

Ascertain the Package

We tin go on filling-in the stub describing the packet past completing the package information tags:

          Proper noun:           python-%{srcname} Version:        %{_version}  Release:        ane%{?dist} Summary:        Total Features Python3 Sample Projection License:        LGPLv3+ Source0:        %{pypi_source}                  

nosotros can safely delete the URL package information tag since in this example we exercise not brand use of information technology. This is the pregnant of each of the to a higher place tags:

  • Proper name: the name of the software existence packaged - it must non contain spaces
  • Version: the version of the software being packaged: it should be as shut as possible to the format of the original software'southward version, although there is the constraint of avoiding o dashes
  • Release: the minor release; notation that information technology is upwards to the package architect to determine which build represents a new release and to update the release manually. Note that likewise here there is the constraint of avoiding o dashes
  • Summary: a i-line description of the packaged software
  • License: the license terms applicative to the software being package
  • Source0: the Source tag has the purpose to both show where the software's developer has made the original sources available and provide the name of the original source file. This means that information technology can be either filename or URL to locate the archive containing the sources. We can specify equally many sources equally we need simply by incrementing the number at the end of the "Source" word.

"%{pypi_source}" is a RPM macro that can be used when having to bargain with sources from PyPI to generate the proper URL: we can evaluate the macro so to see how it gets expanded by just specifying the "--eval" control switch of the rpm command line utility:

          rpm --eval "%pypi_source carcano_foolist 0.0.9-py3-none-any whl"        

the output is every bit follows:

          https://files.pythonhosted.org/packages/source/c/carcano_foolist/carcano_foolist-0.0.9-py3-none-whatsoever.whl"        

RPM macros are snippets of "lua" linguistic communication lawmaking: you lot tin get the dump of all of them, along with their lua code, by but specifying the "--showrc" command switch of the rpmcommand line utility as follows:

          rpm --showrc        

if you want to focus on the lua code of a sure macro, yous have just to pipe it to sed - for example, to see the source code of "%{pypi_source}" macro we previously saw:

          rpm --showrc | sed -n -due east '/pypi_source/,/}/ p'        

the output is:

          -13: pypi_source	%{lua:     local src = rpm.expand('%ane')     local ver = rpm.expand('%2')     local ext = rpm.expand('%3')     local url = rpm.aggrandize('%__pypi_url')      -- If no first argument, try %srcname, then %pypi_name, so %name     -- Annotation that rpm leaves macros unchanged if they are non defined.     if src == '%i' then         src = rpm.expand('%srcname')     stop     if src == '%srcname' then         src = rpm.expand('%pypi_name')     end     if src == '%pypi_name' then         src = rpm.expand('%name')     cease      -- If no second argument, use %version     if ver == '%ii' and so         ver = rpm.expand('%version'):gsub('~', '')     stop      -- If no third statement, apply the preset default extension     if ext == '%3' and so         ext = rpm.expand('%__pypi_default_extension')     end      local first = string.sub(src, ane, 1)      print(url .. first .. '/' .. src .. '/' .. src .. '-' .. ver .. '.' .. ext) }        

From the code we can gauge that if we want to use a different URL we accept to set it by defining the "%__pypi_url" macro before using "%{pypi_source}" macro.

Now that we have a better agreement of RPM macros, we tin continue defining / completing the following package information tags:

          BuildArch:       noarch BuildRequires:   python3-devel python3-setuptools Requires:        python3                  

this is the meaning of each of the above bundle information tags:

  • BuildArch: the architecture the software is targeted to
  • BuildRequires: the RPM packages that are required to build this RPM
  • Requires: the packages that are required to run the software packaged within this RPM - they can be either RPM package or virtual packages provided by ane or more than RPM packages that apply the provided tag. Annotation that version comparisons may besides be included past following the bundle name with <, >, =, >=, or <=, and a version specification

then we complete the description tag as follows - note how this tag is multi-line so to let us provide a thorough description:

          %description Full Featured Python3 Sample Project                  

Ascertain the SubPackages

What nosotros have washed so far has the sole purpose of defining the tags for the base RPM bundle: in this project we create two different RPM sub-packages.

Package information tags that are within a "%package" department are considered in the scope of the subpackage specified by the -due north argument of the "%package" section.

Our kickoff subpackage is "python3-carcano_foolist-common": it's purpose is packaging only the modules delivered past the "carcano_foolist" Python package itself :

          %package -n python3-%{srcname}-common Summary:        %{summary} BuildRequires:  python3-devel %{?python_provide:%python_provide python3-%{srcname}-common}  %description -n python3-%{srcname}-common Python3 packages of the sample projection        

the lines to explain hither are:

  • the onest is used do declare the subpackage using the "%package" data tag  (note the "-due north" option)
  • the 4th is used to automatically gauge the provided Python packages
  • the half dozenth is used to provide the description of the subpackage using the "%description" data tag with the "-due north" choice followed by the name of the subpackage

The second and last subpackage is "python3-carcano_foolist" - let's ascertain it as follows:

          %bundle -due north python3-%{srcname} Summary:        %{summary} BuildRequires:  python3-devel Requires:       python3-%{srcname}-common %{?python_provide:%python_provide python3-%{srcname}}  %description -n python3-%{srcname} Python3 scripts and resources of the sample project        

This ends the part related to the definition of the package information tags.

Prepare the Build (the %prep Stage)

We are getting closer to the more than interesting parts - permit's complete the %prep department as follows:

          %prep %autosetup -n %{srcname}-%{_version}        

this section is executed as the %prepstage  of the RPM building process. Information technology runs the "%autosetup" macro specifying the name of the directory where to extract the contents of the package into ("-northward" choice).

Build the Package (the %build Stage)

Nosotros are now ready for the actual build process: it is defined in the %build section - replace the default contents of the stub  and complete it as follows:

          %build unset RPM_BUILD_ROOT %{__python3} setup.py bdist_wheel        

this department is executed every bit the %buildstage  of the RPM edifice process.

The official Red Chapeau and Fedora documentation recommend using the %py3_build family macros (py3_build, %py3_build_wheel, ...). In my lab I've not been able to comply with the recommendation, since I got an fault from setuptools complaining that it cannot import the name 'find_namespace_packages'. Afterwards inspecting the failing temporary file generated past the expansion of the macro, I establish that things broke after setting the RPM_BUILD_ROOT surroundings variable. For this reason my workaround is to unset the RPM_BUILD_ROOT environs variable and explicitly run the build command.

Install the Bundle into a unlike root directory tree (the %install Phase)

We are somewhen at the install procedure: it is defined in the %install section - replace the default contents and complete it equally follows:

The purpose of the install process is to install the files into a root directory tree different from the actual root directory - it runs during the %install stage :

          %install [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} mkdir %{buildroot} mkdir %{buildroot}/usr cd "%{_builddir}/%{srcname}-%{_version}/dist" %{__python3} -thousand pip install --target %{buildroot}%{python3_sitelib} %{srcname}-%{_version}-py3-none-any.whl  mkdir %{buildroot}/%{_sysconfdir} mkdir %{buildroot}/%{_sysconfdir}/fooapp mkdir %{buildroot}/%{_sysconfdir}/rsyslog.d mkdir %{buildroot}/usr/bin cp %{_builddir}/%{srcname}-%{_version}/bin/logging.conf %{buildroot}/%{_sysconfdir}/fooapp/logging.conf cp %{_builddir}/%{srcname}-%{_version}/share/doc/fooapp/rsyslog/fooapp.conf %{buildroot}/%{_sysconfdir}/rsyslog.d/fooapp.conf cp %{_builddir}/%{srcname}-%{_version}/bin/fooapp.py %{buildroot}/usr/bin/fooapp.py        

Also here official recommendation from Red Hat and Fedora documentation to utilize the %py3_build family macros  (py3_install, %py3_install_wheel, ...) failed on my lab: same symptom as before, fixed by  unset the RPM_BUILD_ROOT environment variable and explicitly run the installation commands.

Run Unit Tests (the %check stage)

As he tbest practise requires, the contents of the Python package must succeed the unit of measurement tests before going on and packaging it as RPM. The section aimed at this is the %check section, so let's create it as follows:

          %check cd "%{_builddir}/%{srcname}-%{_version}" unset RPM_BUILD_ROOT %{__python3} setup.py nosetests >/dev/zip        

information technology runs during the %bank check phase .

List The Files To Be Packed Into Each Of The SubPackages

Now that the Python package has been prepared extracting its contents, congenital, installed into a chrootd tree and tests information technology has eventually come the time to pack the outcome into the RPM packages: the purpose of the %files sections is exactly to list the files that must exist put into each of the packages.
This is the snippet of the %files section that specifies which files must exist put inside the python3-carcano_foolist-common RPM package:

          %files -northward python3-%{srcname}-common %{python3_sitelib}/carcano/foolist/__init__.py %{python3_sitelib}/carcano/foolist/__pycache__/__init__.cpython-36.opt-1.pyc %{python3_sitelib}/carcano/foolist/__pycache__/__init__.cpython-36.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolist.cpython-36.opt-i.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolist.cpython-36.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolistitem.cpython-36.opt-1.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolistitem.cpython-36.pyc %{python3_sitelib}/carcano/foolist/foolist.py %{python3_sitelib}/carcano/foolist/foolistitem.py %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/INSTALLER %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/METADATA %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/Record %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/Cycle %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/top_level.txt        

This is the snippet of the %files section that specifies which files must be put inside the python3-carcano_foolist RPM package:

          %files -n python3-%{srcname} %config  %{_sysconfdir}/fooapp/logging.conf %config  %{_sysconfdir}/rsyslog.d/fooapp.conf /usr/bin/fooapp.py                  

Add together Post-Installation statements

The RPM package python3-carcano_foolist requires also to execute a few mail installation statements: since information technology provides an additional configuration file to rsyslog, it besides requires reloading rsyslog to utilise the new configuration.

Post-installation tasks are defined within the %mail service section, where every bit tasks that must run afterwards an uninstall should be put into %postun section:

          %post -n python3-%{srcname} systemctl restart rsyslog  %postun -north python3-%{srcname} systemctl restart rsyslog        

of grade we must scope the %mail and %postun sections so as to refer them only to the "python3-carcano_foolist" RPM parcel.

Add the Changelog

The %changelog section is used to provide details most the development of the packaged software beyond the releases.

          %changelog * Mon Jun fourteen 2021 Marco Antonio Carcano <me@mydomain.tld> Get-go release        

Of grade it is mandatory to make full-in information technology, but exercise non underestimate it: yous must be tidy, since it can provide valuable data to who is installing the RPM package.

The whole SPEC file

For your convenience, this is the total listing of "RPM/SPECS/carcano_foolist.spec" file:

          %{!?_version: %ascertain _version 0.0.1 } %global srcname carcano_foolist  Name:           python-%{srcname} Version:        %{_version}  Release:        1%{?dist} Summary:        Full Features Python3 Sample Project License:        LGPLv3+ Source0:        %{pypi_source}  BuildArch:       noarch BuildRequires:   python3-devel python3-setuptools Requires:        python3  %description Full Featured Python3 Sample Project  %package -n python3-%{srcname}-common Summary:        %{summary} BuildRequires:  python3-devel %{?python_provide:%python_provide python3-%{srcname}-mutual}  %description -n python3-%{srcname}-common Python3 packages of the sample project  %package -northward python3-%{srcname} Summary:        %{summary} BuildRequires:  python3-devel Requires:       python3-%{srcname}-common %{?python_provide:%python_provide python3-%{srcname}}  %clarification -north python3-%{srcname} Python3 scripts and resources of the sample project  %prep %autosetup -n %{srcname}-%{_version}  %build unset RPM_BUILD_ROOT %{__python3} setup.py bdist_wheel  %install [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} mkdir %{buildroot} mkdir %{buildroot}/usr cd "%{_builddir}/%{srcname}-%{_version}/dist" %{__python3} -k pip install --target %{buildroot}%{python3_sitelib} %{srcname}-%{_version}-py3-none-any.whl  mkdir %{buildroot}/%{_sysconfdir} mkdir %{buildroot}/%{_sysconfdir}/fooapp mkdir %{buildroot}/%{_sysconfdir}/rsyslog.d mkdir %{buildroot}/usr/bin cp %{_builddir}/%{srcname}-%{_version}/bin/logging.conf %{buildroot}/%{_sysconfdir}/fooapp/logging.conf cp %{_builddir}/%{srcname}-%{_version}/share/doc/fooapp/rsyslog/fooapp.conf %{buildroot}/%{_sysconfdir}/rsyslog.d/fooapp.conf cp %{_builddir}/%{srcname}-%{_version}/bin/fooapp.py %{buildroot}/usr/bin/fooapp.py  %bank check cd "%{_builddir}/%{srcname}-%{_version}" unset RPM_BUILD_ROOT %{__python3} setup.py nosetests >/dev/null  %files -n python3-%{srcname}-common %{python3_sitelib}/carcano/foolist/__init__.py %{python3_sitelib}/carcano/foolist/__pycache__/__init__.cpython-36.opt-ane.pyc %{python3_sitelib}/carcano/foolist/__pycache__/__init__.cpython-36.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolist.cpython-36.opt-1.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolist.cpython-36.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolistitem.cpython-36.opt-1.pyc %{python3_sitelib}/carcano/foolist/__pycache__/foolistitem.cpython-36.pyc %{python3_sitelib}/carcano/foolist/foolist.py %{python3_sitelib}/carcano/foolist/foolistitem.py %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/INSTALLER %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/METADATA %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/RECORD %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/WHEEL %{python3_sitelib}/carcano_foolist-%{_version}.dist-info/top_level.txt  %files -due north python3-%{srcname} %config  %{_sysconfdir}/fooapp/logging.conf %config  %{_sysconfdir}/rsyslog.d/fooapp.conf /usr/bin/fooapp.py  %post -northward python3-%{srcname} systemctl restart rsyslog  %postun -n python3-%{srcname} systemctl restart rsyslog  %changelog * Mon Jun fourteen 2021 Marco Antonio Carcano <me@mydomain.tld> Starting time release                  

since we are using nose to perform unit of measurement-tests, we must ensure that information technology is installed:

          sudo dnf install -y python3-olfactory organ        

in improver to that, let's ensure we have an as current equally possible version of bike and setuptools:

          sudo python3 -k pip install -U bike setuptools        

Add together the "rpm" target to the Makefile

Every bit we did in the previous post, since we love to work within the context of a Continuous Integration toolchain, allow's modify the Makefile we created so far past calculation the "rpm" target every bit follows:

          rpm: sdist 	$(info -> Makefile: packaging as RPM ...) 	[ -d ~/rpmbuild ] || mkdir ~/rpmbuild 	[ -d ~/rpmbuild/SOURCES ] || mkdir ~/rpmbuild/SOURCES 	[ -d ~/rpmbuild/SPECS ] || mkdir ~/rpmbuild/SPECS 	mv src/dist/carcano_foolist-${RELEASE}.tar.gz ~/rpmbuild/SOURCES 	cp RPM/SPECS/carcano_foolist.spec ~/rpmbuild/SPECS 	cd ~/rpmbuild/SPECS; rpmbuild --define "_version ${RELEASE}" -ba carcano_foolist.spec        

nosotros tin now build everything every bit a whole as follows:

          export RELEASE="0.0.2" make rpm        

Take in account that in the SPEC file we specified "BuildRequires python3-devel", ... this means that y'all have to install it before being able to build these RPMs.

if everything has properly been setup, the output is as follows:

          -> Makefile: validating RELEASE=0.0.2 format -> Makefile: cleanup previous builds ...  -> Makefile: edifice the sdist distribution package ... running sdist running egg_info creating carcano_foolist.egg-info writing carcano_foolist.egg-info/PKG-INFO ... Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/grimoire/rpmbuild/BUILDROOT/python-carcano_foolist-0.0.two-1.el8.x86_64 Wrote: /home/grimoire/rpmbuild/SRPMS/python-carcano_foolist-0.0.2-1.el8.src.rpm Wrote: /home/grimoire/rpmbuild/RPMS/noarch/python3-carcano_foolist-common-0.0.two-ane.el8.noarch.rpm Wrote: /dwelling house/grimoire/rpmbuild/RPMS/noarch/python3-carcano_foolist-0.0.2-1.el8.noarch.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.0G9mQa + umask 022 + cd /dwelling/grimoire/rpmbuild/BUILD + cd carcano_foolist-0.0.2 + /usr/bin/rm -rf /dwelling house/grimoire/rpmbuild/BUILDROOT/python-carcano_foolist-0.0.2-ane.el8.x86_64 + exit 0        

Nosotros at present have a consummate project we can use into a continuous improvement loop: this means that nosotros can become-on with the regular development cycle, fixing errors and adding features when needed and rebuilding things.

Digitally sign the RPM package

Before publishing an RPM parcel into a repository from where it tin exist downloaded, we must digitally sign it: past doing and then, tools such as yum, dnf, attachment and such tin verify the signature past using the public key that we publish.

The default behaviour of yum and dnf is abolish the installation process of unsigned packages: despite you can reconfigure them to skip this check, or utilise an option to make them temporarily skip it, it is not wise to install unsigned packages, ... for the same reason it is non wise to take candy from strangers.

Generate The GPG Key

Signing is made using the GNU Privacy Guard (GPG), so the very offset thing is generating the GNU Privacy Guard (GPG) fundamental pair used to sign the RPM packages.

These keys are used to sign every parcel built by the corporation: this means that the value of the "existent name" must exist something that identifies the whole corporation's development facilities. For this blog I'm using "The Grimoire Of A Modern Linux Professional person" as my existent proper name and "ci@grimoire.carcano.ch" equally email, pretending this is the email address of the corporate's continuous integration toolchain.

Let's create the GPG cardinal pair by entering the following control:

          gpg --skilful --pinentry-manner loopback --full-gen-key        

Note the utilize of "--pinentry-mode loopback" control pick: information technology is needed when working equally user we have been been switched to past using sudo. I wanted to show information technology since the common exercise is to get-go login via SSH using the corporate user account, and then switch to a service account (for example the 1 of continuous integration). In such a scenario, if you omit this option, several failures because of permission violations happens.

This is the transcript of my reply to the questions:

          Please select what kind of key y'all want:    (one) RSA and RSA (default)    (two) DSA and Elgamal    (three) DSA (sign only)    (four) RSA (sign only)    (7) DSA (set your own capabilities)    (8) RSA (set up your own capabilities)    (9) ECC and ECC   (ten) ECC (sign only)   (11) ECC (prepare your own capabilities)   (xiii) Existing primal   (14) Existing key from card Your choice? 1 RSA keys may exist between 1024 and 4096 bits long. What keysize do you want? (2048)  Requested keysize is 2048 bits RSA keys may exist betwixt 1024 and 4096 $.25 long. What keysize do y'all want for the subkey? (2048)  Requested keysize is 2048 bits Delight specify how long the cardinal should be valid.          0 = key does not expire         = key expires in n days       w = key expires in n weeks       m = key expires in n months       y = key expires in n years Fundamental is valid for? (0) 10y Key expires at Sat 25 Oct 2031 05:00:56 PM UTC Is this correct? (y/N) y  GnuPG needs to construct a user ID to place your key.  Existent name: The Grimoire Of A Mod Linux Professional Email accost: ci@grimoire.carcano.ch Comment:  You selected this USER-ID:     "The Grimoire Of A Modernistic Linux Professional <ci@grimoire.carcano.ch>"  Change (North)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o        

Allow'southward ostend typing "o", and wait for everything to complete.

Let's at present have a wait at the generated keys:

          gpg --list-keys        

the output is as follows:

          gpg: checking the trustdb gpg: marginals needed: 3 completes needed: 1 trust model: pgp gpg: depth: 0 valid: one signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u gpg: next trustdb check due at 2031-10-25 /home/grimoire/.gnupg/pubring.kbx --------------------------------- pub rsa2048 2021-ten-27 [SC] [expires: 2031-10-25] 2A6018DE3B4282CE229A897E5BA662C0EBD6F747 uid [ultimate] The Grimoire Of A Modern Linux Professional <ci@grimoire.carcano.ch> sub rsa2048 2021-ten-27 [E] [expires: 2031-ten-25]        

The public key related to the private one used to sign must exist published on a repository that can be accessed by everybody who needs to install RPM packages signed by u.s.: otherwise they accept no fashion to check the signature.

This mean that nosotros must export the public fundamental equally follows:

          gpg --export -a ci@grimoire.carcano.ch > ~/RPM-GPG-Key-grimoire                  

and publish information technology on the repository.

Sign the RPM Package

The signing feature for the rpm command line utility is provided by the rpm-sign RPM parcel - let'due south install it as follows:

          sudo dnf install -y rpm-sign        

then we have to configure the RPM macros of the current user:

  • since you can have several keys within your GPG keyring, we must specify which GPG key must be used to sign RPMs: here we utilize "ci@grimoire.carcano.ch", the GPG key we just created
  • although this is optional, the GPG command we utilise to sign - here I'm doing this since I wanted to add the "--pinentry-fashion loopback"
          cat << \EOF >> ~/.rpmmacros %_gpg_name <ci@grimoire.carcano.ch> %__gpg_sign_cmd %{__gpg} gpg --force-v3-sigs --no-armor --pinentry-mode loopback --no-secmem-alarm -u "%{_gpg_name}" -sbo %{__signature_filename} --digest-algo sha256 %{__plaintext_filename}' EOF        

now we have setup everything and nosotros are eventually fix to sign the packages - the command is as follows:

          rpm --addsign ~/rpmbuild/RPMS/noarch/python3-carcano_foolist*.rpm        

after typing the password to unlock the secret key for the signature, the RPM packages get signed.

We tin of course add a "sign" target to the Makefile:

          sign: 	$(info -> Makefile: digitally signing the RPM packages...) 	rpm --addsign ~/rpmbuild/RPMS/noarch/python3-carcano_foolist*.rpm        

if you are using a RPM repository such as Red Hat Network Satellite Server and you have a channel dedicated to the delivery of the software you lot develop, this target is too suitable for the purpose of pushing the signed RPM packet to them too. That is: yous sign your software and immediately publish it.

Only as an case, this is the command nosotros'd use to button to Red Hat Network Satellite 6.x (or Katello):

          hammer repository upload-content --organisation Grimoire --production Carcano-Stuff --path ~/rpmbuild/RPMS/noarch/python3-carcano_foolist*.rpm --name my-amazing-software-repo        

search the spider web for "configure passwordless login for hammer" if you don't desire to interactively blazon the credentials to login to the Red Hat Network Satellite Server.

This is the command we'd use to push to Carmine Lid Network Satellite 5.10 (or Spacewalk):

          rhnpush --username=ciuser --aqueduct "my-amazing-software-channel" --nosign --server=<FQDN> ~/rpmbuild/RPMS/noarch/python3-carcano_foolist*.rpm        

to run the sign target of the Makefile, simply blazon:

          consign RELEASE="0.0.two" make sign        

again, correct after typing the countersign to unlock the secret key for the signature, the RPM packages get signed.

Verify the Signature of the Packet

Let'due south at present pretend to be the ones that demand to install the RPM package: since the delivered RPM bundle is signed, we must import the public primal used to sign it into an RPM database:

          sudo rpm --import ~/RPM-GPG-Fundamental-grimoire        

we are now gear up to install the RPM bundle using "yum", "dnf" or even the simple "rpm" command tool.

If we are not interested into installing information technology, but nosotros anyhow wot to check the signature, simply blazon:

          rpm --checksig ~/rpmbuild/RPMS/noarch/python3-carcano_foolist*.rpm        

the output is as follows:

          /abode/grimoire/rpmbuild/RPMS/noarch/python3-carcano_foolist-0.0.2-1.el8.noarch.rpm: digests signatures OK /home/grimoire/rpmbuild/RPMS/noarch/python3-carcano_foolist-common-0.0.2-1.el8.noarch.rpm: digests signatures OK        

the RPM packet is signed and the signature is valid if the word "signatures OK" appears at the end of the  line.

Footnotes

Here it ends the tertiary and final part of this trilogy of posts defended to Python: I hope you enjoyed it. Y'all at present also know how to create an RPM packet. I hope you enjoyed the whole trilogy, and that it provided you with good hints about how to set up and manage your Python projects.

Many years agone, as a child I saw an interview with Freddie Mercury in which he said "whatever you do, practice information technology with mode". I was really impressed by that sentence: he was right - doing things and doing things with style is a completely different matter.

In this trilogy I wanted to share my style, ... probably there are things that tin be improved a scrap more, but at least setting things like so has proved to be a make clean and like shooting fish in a barrel way of doing, that promotes maintainability. I hope you appreciate the strive.

jamespeare1953.blogspot.com

Source: https://grimoire.carcano.ch/blog/packaging-a-python-wheel-as-rpm/

0 Response to "Easy Way to Make an Rpm Out of a Python Script"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel