Do you use CentOS or any other rpm based Linux distribution to host Java applications or services?
In this article you will see how Gradle can be used for that purpose, in particular:
- what are the naming convention that can be used with RPM
- which tools can help with rpm generation with Gradle
The Desired Flow
The goal is to use yum installer to download, install or update java application or service. Yum uses rpm version comparison to detect updated versions of packages. In order to be able to achieve this goal, it is important to understand how rpm compares versions.
Rpm Version Comparison
There are many articles on the internet, hence I will stay DRY and provide some links that can be useful:
- Red Hat’s rpm packaging guide – explains rpm file naming conventions
- Fedora’s wiki on rpm version comparison (archive, however puts some light on version comparison)
- blog entry by Jason Antman about rpm version comparison
- rpm version comparison tests cases
Another useful thing to do could be to try it yourself. One option is to install rpmdevtools (might require enabling EPEL repository in CentOS): yum install rpmdevtools and run rpmdev-vercmp function. There is also a python implementation of the rpm version compare function. Clone git repository and have fun comparing strings C-like!
Choosing RPM Package Name
According to Red Hat’s guidelines:
Name | The base name of the package, which should match the SPEC file name. |
Version | The upstream version number of the software. |
Release | The number of times this version of the software was released. Normally, set the initial value to 1%{?dist}, and increment it with each new release of the package. Reset to 1 when a new Version of the software is built. |
- Name can be the name of the software (application or service) that is being released.
- Version could be the current version of the software. This can be taken form gradle version or git tag or actually any place where gradle can read from.
- Release explains how many times a package with that version has been built. It can serve the same purpose as SNAPSHOT in maven if there is a need to release the same version of the application and install it many times with yum. This could hold true for development integration environment where subsequent pull requests are being merged to a development branch.
Gradle Implementation
Gradle has 2 plugins that can support rpm generation:
- Netflix Build Language Plugin: ospackage
- Palantir’s git-version
Netflix’s ospackage
The ospackage plugin allows rpm generation with Gradle. It has special values to define version, release, package name and other RPM related metadata. It comes with a predefined buildRpm task. See documentation for more details.
Palantir’s git-version
Git-version allows to use git information to add to the package name. According to current documentation the defaults are as follows:
- packageName – Default to project.name
- version – Version field, defaults to project.version
One example of version and release naming can be the follwoing algorithm:
- Mark next release on a development branch with a release candidate git tag: rc@1.0.0 for example.
- Use this tag as the Version. Git-version allows searching tags only with certain prefixes if needed, which enables ignoring any other tags.
- Use commit distance from the tag as the Release. Since Release cannot be 0 (due to the rpm comparison function) add one to it. Additionally git hash can be appended to have complete view of where the code came from.
Example configuration
Below is the implementation of the algorithm described above. Notice highlighted values from ospackage plugin – there can be many more.
ospackage { def details = versionDetails(prefix: 'rc@') version details.lastTag release details.commitDistance + 1 + '.' + details.gitHash arch X86_64 os LINUX into '/opt/gradle-rpm-example' from(jar.outputs.files) { into '' } }
Now it is time to run gradle buildRpm. Examine build/distributions folder for rpms. Some examples of generated packages with git tag rc@1.0.0 and gradle project of name gradle-rpm-example:
gradle-rpm-example-1.0.0-1.1502f6cdf2.x86_64.rpm gradle-rpm-example-1.0.0-2.ce3bc954a7.x86_64.rpm
Installation with yum
Assuming that these rpms have been stored in a yum repository (for example nexus), and this repository has been added to yum, given the fact that this example follows rpm comparison rules, subsequent rpms will be visible by yum as updates of the application or service. To list available yum repositories run yum repolist. Example output might be:
repo id | repo name | status base/7/x86_64 CentOS-7 - Base 9591 extras/7/x86_64 CentOS-7 - Extras 435 nexusrepo Nexus Repository 4 updates/7/x86_64 CentOS-7 - Updates 2404
It is worth to remember that yum uses local cache, hence it is worth invalidating it if the updates are not visible.
yum clean expire-cache yum makecache
To list available packages run
yum --disablerepo="*" --enablerepo="nexusrepo" list available
This command can generate output similar to the one below:
Available Packages gradle-rpm-example.x86_64 1.0.0-1.1502f6cdf2 nexusrepo
To install the package run
yum install --nogpgcheck gradle-rpm-example
The –nogpgcheck option shouldn’t be used in real life if the author of the package is not known. The reason it is used here is that the sample package is not signed.
If the second package is now uploaded to yum repo, after refreshing yum cache and running yum info gradle-rpm-example it is clear that there is an update available:
Installed Packages Name : gradle-rpm-example Arch : x86_64 Version : 1.0.0 Release : 1.1502f6cdf2 Size : 0.0 Repo : installed From repo : nexusrepo Available Packages Name : gradle-rpm-example Arch : x86_64 Version : 1.0.0 Release : 2.ce3bc954a7 Size : 2.5 k Repo : nexusrepo
Running yum update –nogpgcheck gradle-rpm-example will update the package and new version will be installed.
Please note that in this example, rpm release numbers can increase by more than one, given there can be more than one commit since the package has last been built. RPM does not impose strict rules on release generation, and unless next release is greater than the previous, yum will handle updates without issues.
Sample Project on GitHub
The example can be downloaded from GitHub repository.
Summary
This post illustrated how to build rpm package with Gradle as well as naming convention that could be used to allow yum updates of subsequent rpm releases.
What are your experiences with Java and RPM? It would be great to see different approaches to rpm generation and installation of software implemented in Java. If you can share your thoughts, please write in comments or contact me directly.
This looks like a great plugin but in my environment only the from/into stuff is working. None of the other directives like requires/directory/preInstall/postInstall etc have any effect whatsoever on the generated RPM. Annoyingly, the scriptlet directive complain if I provide an invalid filename, but they do absolutely nothing with a valid filename!
LikeLike