Objective
Our goal is to build rpm packages with custom content, unifying scripts across any number of systems, including versioning, deployment and undeployment.
Operating System and Software Versions
- Operating system: Red Hat Enterprise Linux 7.5
- Software: rpm-build 4.11.3+
Requirements
Privileged access to the system for install, normal access for build.
Difficulty
MEDIUM
Conventions
- # – requires given linux commands to be executed with root privileges either directly as a root user or by use of
sudo
command - $ – given linux commands to be executed as a regular non-privileged user
Introduction
One of the core feature of any Linux system is that they are built for automation. If a task may need to be executed more than one time – even with some part of it changing on next run – a sysadmin is provided with countless tools to automate it, from simple shell
scripts run by hand on demand (thus eliminating typo errors, or only save some keyboard hits) to complex scripted systems where tasks run from cron
at a specified time, interacting with each other, working with the result of another script, maybe controlled by a central management system etc.
While this freedom and rich toolset indeed adds to productivity, there is a catch: as a sysadmin, you write a useful script on a system, which proves to be useful on another, so you copy the script over. On a third system the script is useful too, but with minor modification – maybe a new feature useful only in that system, reachable with a new parameter. Generalization in mind, you extend the script to provide the new feature, and complete the task it was written for as well. Now you have two versions of the script, the first is on the first two systems, the second in on the third system.
You have 1024 computers running in the datacenter, and 256 of them will need some of the functionality provided by that script. In time you will have 64 versions of the script all over, every version doing its job. On the next system deployment you need a feature you recall you coded at some version, but which? And on which systems are they?
On RPM based systems, such as Red Hat flavors, a sysadmin can take advantage of the package manager to create order in the custom content, including simple shell scripts that may not provide else but the tools the admin wrote for convenience.
In this tutorial we will build a custom rpm for Red Hat Enterprise Linux 7.5 containing two bash
scripts, parselogs.sh
and pullnews.sh
to provide a way that all systems have the latest version of these scripts in the /usr/local/sbin
directory, and thus on the path of any user who logs in to the system.
Distributions, major and minor versions
In general, the minor and major version of the build machine should be the same as the systems the package is to be deployed, as well as the distribution to ensure compatibility. If there are various versions of a given distribution, or even different distributions with many versions in your environment (oh, joy!), you should set up build machines for each. To cut the work short, you can just set up build environment for each distribution and each major version, and have them on the lowest minor version existing in your environment for the given major version. Of cause they don’t need to be physical machines, and only need to be running at build time, so you can use virtual machines or containers.
In this tutorial our work is much easier, we only deploy two scripts that have no dependencies at all (except bash
), so we will build noarch
packages which stand for “not architecture dependent”, we’ll also not specify the distribution the package is built for. This way we can install and upgrade them on any distribution that uses rpm
, and to any version – we only need to ensure that the build machine’s rpm-build
package is on the oldest version in the environment.
Setting up building environment
To build custom rpm packages, we need to install the rpm-build
package:
# yum install rpm-build
From now on, we do not use root
user, and for a good reason. Building packages does not require root
privilege, and you don’t want to break your building machine.
Building the first version of the package
Let’s create the directory structure needed for building:
$ mkdir -p rpmbuild/SPECS
Our package is called admin-scripts, version 1.0. We create a specfile
that specifies the metadata, contents and tasks performed by the package. This is a simple text file we can create with our favorite text editor, such as vi
. The previously installed rpmbuild
package will fill your empty specfile with template data if you use vi
to create an empty one, but for this tutorial consider the specification below called admin-scripts-1.0.spec
:
Name: admin-scripts
Version: 1
Release: 0
Summary: FooBar Inc. IT dept. admin scripts
Packager: John Doe
Group: Application/Other
License: GPL
URL: www.foobar.com/admin-scripts
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
%description
Package installing latest version the admin scripts used by the IT dept.
%prep
%setup -q
%build
%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local/sbin
cp scripts/* $RPM_BUILD_ROOT/usr/local/sbin/
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
%dir /usr/local/sbin
/usr/local/sbin/parselogs.sh
/usr/local/sbin/pullnews.sh
%doc
%changelog
* Wed Aug 1 2018 John Doe
- release 1.0 - initial release
Place the specfile in the rpmbuild/SPEC
directory we created earlier.
We need the sources referenced in the specfile
– in this case the two shell scripts. Let’s create the directory for the sources (called as the package name appended with the main version):
$ mkdir -p rpmbuild/SOURCES/admin-scripts-1/scripts
And copy/move the scripts into it:
$ ls rpmbuild/SOURCES/admin-scripts-1/scripts/
parselogs.sh pullnews.sh
As this tutorial is not about shell scripting, the contents of these scripts are irrelevant. As we will create a new version of the package, and the pullnews.sh
is the script we will demonstrate with, it’s source in the first version is as below:
#!/bin/bash
echo "news pulled"
exit 0
Do not forget to add the appropriate rights to the files in the source – in our case, execution right:
chmod +x rpmbuild/SOURCES/admin-scripts-1/scripts/*.sh
Now we create a tar.gz
archive from the source in the same directory:
cd rpmbuild/SOURCES/ && tar -czf admin-scripts-1.tar.gz admin-scripts-1
We are ready to build the package:
rpmbuild --bb rpmbuild/SPECS/admin-scripts-1.0.spec
We’ll get some output about the build, and if anything goes wrong, errors will be shown (for example, missing file or path). If all goes well, our new package will appear in the RPMS directory generated by default under the rpmbuild
directory (sorted into subdirectories by architecture):
$ ls rpmbuild/RPMS/noarch/ admin-scripts-1-0.noarch.rpm
We have created a simple yet fully functional rpm package. We can query it for all the metadata we supplied earlier:
$ rpm -qpi rpmbuild/RPMS/noarch/admin-scripts-1-0.noarch.rpm
Name : admin-scripts
Version : 1
Release : 0
Architecture: noarch
Install Date: (not installed)
Group : Application/Other
Size : 78
License : GPL
Signature : (none)
Source RPM : admin-scripts-1-0.src.rpm
Build Date : 2018. aug. 1., Wed, 13.27.34 CEST
Build Host : build01.foobar.com
Relocations : (not relocatable)
Packager : John Doe
URL : www.foobar.com/admin-scripts
Summary : FooBar Inc. IT dept. admin scripts
Description :
Package installing latest version the admin scripts used by the IT dept.
And of cause we can install it (with root
privileges):
As we installed the scripts into a directory that is on every user’s $PATH
, you can run them as any user in the system, from any directory:
$ pullnews.sh
news pulled
The package can be distributed as it is, and can be pushed into repositories available to any number of systems. To do so is out of the scope of this tutorial – however, building another version of the package is certainly not.
Building another version of the package
Our package and the extremely useful scripts in it become popular in no time, considering they are reachable anywhere with a simple yum install admin-scripts
within the environment. There will be soon many requests for some improvements – in this example, many votes come from happy users that the pullnews.sh
should print another line on execution, this feature would save the whole company. We need to build another version of the package, as we don’t want to install another script, but a new version of it with the same name and path, as the sysadmins in our organization already rely on it heavily.
First we change the source of the pullnews.sh
in the SOURCES to something even more complex:
#!/bin/bash echo "news pulled" echo "another line printed" exit 0
We need to recreate the tar.gz with the new source content – we can use the same filename as the first time, as we don’t change version, only release (and so the Source0
reference will be still valid). Note that we delete the previous archive first:
cd rpmbuild/SOURCES/ && rm -f admin-scripts-1.tar.gz && tar -czf admin-scripts-1.tar.gz admin-scripts-1
Now we create another specfile with a higher release number:
cp rpmbuild/SPECS/admin-scripts-1.0.spec rpmbuild/SPECS/admin-scripts-1.1.spec
We don’t change much on the package itself, so we simply administrate the new version as shown below:
Name: admin-scripts Version: 1 Release: 1 Summary: FooBar Inc. IT dept. admin scripts Packager: John DoeGroup: Application/Other License: GPL URL: www.foobar.com/admin-scripts Source0: %{name}-%{version}.tar.gz BuildArch: noarch %description Package installing latest version the admin scripts used by the IT dept. %prep %setup -q %build %install rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/usr/local/sbin cp scripts/* $RPM_BUILD_ROOT/usr/local/sbin/ %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %dir /usr/local/sbin /usr/local/sbin/parselogs.sh /usr/local/sbin/pullnews.sh %doc %changelog * Wed Aug 22 2018 John Doe - release 1.1 - pullnews.sh v1.1 prints another line * Wed Aug 1 2018 John Doe - release 1.0 - initial release
All done, we can build another version of our package containing the updated script. Note that we reference the specfile with the higher version as the source of the build:
rpmbuild --bb rpmbuild/SPECS/admin-scripts-1.1.spec
If the build is successful, we now have two versions of the package under our RPMS directory:
ls rpmbuild/RPMS/noarch/
admin-scripts-1-0.noarch.rpm admin-scripts-1-1.noarch.rpm
And now we can install the “advanced” script, or upgrade if it is already installed.
And our sysadmins can see that the feature request is landed in this version:
rpm -q --changelog admin-scripts * Wed aug 22 2018 John Doe- release 1.1 - pullnews.sh v1.1 prints another line * Wed aug 01 2018 John Doe - release 1.0 - initial release
Conclusion
We wrapped our custom content into versioned rpm packages. This means no older versions left scattered across systems, everything is in it’s place, on the version we installed or upgraded to. RPM gives the ability to replace old stuff needed only in previous versions, can add custom dependencies or provide some tools or services our other packages rely on. With effort, we can pack nearly any of our custom content into rpm packages, and distribute it across our environment, not only with ease, but with consistency.