Introduction à la création de paquets avec RPM
21 septembre 2015

Introduction à la création de paquets avec RPM

Rédigé par - Répondre

Share Button

Julien, membre de la communauté de blogueurs JobProd.

Si vous avez travaillé avec des Linux à base RedHat comme RHEL ou encore CentOS, vous avez probablement entendu parler des RPM. Il s’agit là du format de paquets d’installation natif à bon nombre de systèmes *n*x, parmi lesquels RedHat, Fedora, CentOS, ou encore AIX et SuSE, cette dernière n’étant pas basée sur RedHat mais sur Slackware, comme quoi elle cache bien son jeu. :-p

L’intérêt principal de RPM sur ces systèmes est qu’il s’agit du système natif d’installation de logiciels sur ces plateformes, en d’autres termes celui qui permettra de s’intégrer le mieux possible à celles-ci, tout en bénéficiant au maximum de leur outillage. Bon, maintenant passons dans le vif du sujet. D’autre part s’adapter au format de packaging de la plateforme cible est une bonne pratique au sens devops.

Le fichier de base des RPM : le fichier .spec

La création des paquets RPM passe par celle d’un fichier .spec, qui décrit à la fois comment est construit le paquet ainsi que d’autres métadonnées et instructions intéressantes, comme par exemple ce qu’il faut faire lors de l’installation ou la désinstallation de ce paquet.

Il est possible de créer un squelette de fichier .spec depuis la commande vim sous RedHat de la manière suivante :

vim .spec

Au passage je suis tellement habitué à cet éditeur que pour enregistrer cet article j’étais à la limite de taper Ctrl+C et :w. :-p !

Les sections d’un manifeste RPM

Un manifeste RPM est divisé en sections, de quatre sortes :

  1. Les sections de documentation, telles que la licence du paquet, et d’autres informations textuelles
  2. Les sections de métadonnées, qui contiennent en particulier la liste des fichiers du paquet.
  3. Les sections de construction, qui détaillent comment le paquet doit être construit.
  4. Les sections d’installation, qui spécifient ce qui doit être fait à l’installation en plus de la simple décompression des fichiers.

L’en-tête du fichier spec

Avant d’étudier ces sections nous allons voir une description de l’en-tête du fichier, du moins les éléments de cet en-tête qui doivent être remplis.

- Name : le nom du paquet RPM, tel qu’il apparaîtra avec des commandes telles que yum. Celui-ci ne doit pas contenir d’espace.
- Summary : une description courte du paquet, par exemple « Hello world package.
- Version : la version du paquet. Pour un logiciel que vous n’avez pas créé, par exemple PHP, cette version doit en fait correspondre à la version du logiciel.
- Release : ce numéro doit être incrémenté à chaque fois que le paquet est créé de nouveau, par exemple parce que vous avez ajouté un patch sur le paquet. Vous remarquerez que le numéro de release se termine par %{?dist}, ce qui correspond à la distribution cible du paquet, qu’il convient de laisser en état.
- Group : une information textuelle à remplir, mais qui est surtout utile pour le rangement du paquet dans une interface graphique. Ce champ est optionnel.
- License : la licence de votre paquet. Il est nécessaire de remplir ce champ. Si c’est un logiciel développé en interne et que vous ne savez pas quoi mettre vous pouvez inscrire quelque chose comme le nom de votre entreprise ou « Proprietary ».
- URL : l’URL du projet. Ce champ est obligatoire
- Source0… SourceN : l’emplacement des sources de votre projet. Il s’agit d’archives au format tar.gz. Souvent vous n’aurez besoin de renseigner que Source0 mais si votre projet contient plusieurs archives il faut toutes les renseigner.
- BuildRequires : les dépendances requises pour faire le build. Mettez un buildrequires par dépendance pour rendre votre fichier SPEC plus lisible. Par exemple on peut trouver gcc, make, la jdk et ainsi de suite, le mieux étant de mettre les noms des paquets requis et leur version sous la forme NomPaquet = Version. La version est totalement optionnelle mais permet de garantir que le build sera reproductible.
- Requires : les dépendances requises par le paquet au moment de l’installation. Là encore le mieux est de mettre directement les paquets, et si besoin leur version, mais à ce niveau c’est optionnel.
Il existe d’autres en-têtes possibles mais ce sont ici les principaux que vous retrouverez systématiquement.

Les sections de documentation

Il existe deux sections de documentation, matérialisées par les tags %description et %changelog. Le tag %description doit être rempli, alors que le tag %changelog peut être gentiment effacé.

Le tag %description doit contenir une description plus complète, par exemple « This package prints hello world on the standard output. ». Pas la peine non plus d’en écrire des romans, et vous pouvez tout à fait faire des renvois à d’autres documentations plus complètes.

Le tag %changelog décrit les changements appliqués à chaque release du paquet. Ce n’est pas nécessaire de le remplir pour des RPMs à usage interne par contre c’est une bonne de le faire dès lors que vous comptez distribuer votre paquet à l’extérieur. Pour chaque change la syntaxe est comme suit :


* date prenom nom <me@email.com> version
- Changement 1
- Changement 2
- Changement 3

Par exemple :


* Mon Nov  8 2004 Joe XXXXX <jorton@xxxxx.com> 5.0.2-6
- fix dom/sqlite enable/without confusion

Les sections de métadonnées

Les sections de métadonnées contiennent en particulier la liste des fichiers que comporte votre RPM, qui est matérialisée par le tag %files. Cette section doit contenir en fait l’ensemble des fichiers de votre RPM, mais bon, rassurez-vous, il y a des moyens de décrire ça simplement :

- Si vous inscrivez /foo/bar ça signifie que votre paquet revendique la propriété du dossier /foo/bar ainsi que tous ses sous-dossiers.
- Si vous inscrivez /foo/bar/* ça signifie que votre paquet ne revendique pas la propriété du dossier /foo/bar mais revendique celle de tous les fichiers et sous dossiers de ce répertoire qui se trouvent dans votre paquet au moment du build.
– Vous pouvez parfaitement utiliser les wildcards, par exemple : /etc/cron.daily/*-monPaquet

Les macros %attr et %defattr

La macro %defattr devrait être la première chose qu’on trouve dans un paquet RPM. Elle indique tout simplement les attributs par défauts de vos fichiers. Son usage typique devrait être comme suit :

%defattr(-,root,root,-)

Dans l’exemple ça signifie que les fichiers conservent par défaut leurs droits (le premier « -« ), qu’ils appartiennent à l’utilisateur root et au groupe root. Le dernier tiret s’applique aux droits des dossiers et doit généralement être laissé à la valeur tiret. A noter que les valeurs de droits doivent être notés sous valeur numérique, par exemple 640.

La macro %attr permet de définir les attributs de dossiers ou de fichiers spécifiques du RPM. Par exemple, prenons le cas suivant :

%attr(640,joe,users) /foo/bar/joe*

Cela signifie que tous les fichiers et dossiers qui matchent le pattern /foo/bar/joe* vont appartenir à l’utilisateur joe et au groupe users, l’utilisateur joe ayant les droits rw et le groupe users le droit r.

Déclarer ses fichiers de configuration avec la macro %config

La macro %config permet d’indiquer que des fichiers sont des éléments de configuration du système. Ainsi lors de l’installation d’une mise à jour de votre paquet RPM ces fichiers auront droit à un traitement spécial, à savoir qu’ils seront soit sauvegardés à côté, soit laissés en l’état avec la nouvelle version du fichier écrite à côté. Bref contrairement aux autres fichiers il est parfaitement licite de modifier ceux-ci à la main.

Si vous voulez que votre fichier de configuration soit sauvegardé à côté de la nouvelle version du fichier, mais que ce soit la nouvelle version qui prenne le dessus, procédez comme suit :

%config /foo/bar/myConfig

Ceci signifie que si lors d’une mise à jour du fichier vous avez effectué des modifications sur le fichier d’origine, celui-ci sera renommé en /foo/bar/myConfig.rpmsave et le nouveau fichier sera mis à sa place.

Maintenant si vous voulez au contraire que votre fichier soit gardé par défaut et que la nouvelle version du fichier soit mise à côté, procédez comme suit :

%config(noreplace) /foo/bar/myConfig

Cela signifie que lors de l’installation d’une mise à jour si vous avez modifié le fichier d’origine, le nouveau fichier sera nommé avec le suffixe .rpmnew, par exemple /foo/bar/myConfig.rpmnew.

A noter que la macro %config ne s’applique que sur des fichiers, par contre elle accepte les wildcards.

Les sections de construction

Les sections de constructions du package servent, comme leur nom l’indique, à construire le paquet à partir des sources. Ces sections sont les suivantes :

- %prep : décrit comment décompresser les sources.
- %build : compilation des sources.
- %install : constitution de l’archive finale.
- %clean : optionnelle, sert à nettoyer l’espace avant le build.
Chacune de ces sections peut contenir du code bash ou des appels à des macros RPM. Voyons maintenant ces sections plus en détail.

La section %prep

La section %prep décrit comment décompresser la ou les archives définies dans les Source0..SourceN du header du RPM. Par défaut celle-ci est simple puisqu’il s’agit d’un appel à la macro %setup comme suit :

%setup -q

Cette commande va tout simplement décompresser l’archive Source0. Néanmoins cette commande n’est pas franchement safe du point de vue maintenance car elle présuppose que l’archive est organisée de telle façon qu’elle n’ait qu’une seule racine nommée d’après le nom et la version du paquet à construire.
Dès lors il est plus sécurisé d’appeler :

%setup -q -n %{name}

Dès lors cela signifie que votre archive a pour racine un dossier portant le nom du paquet, et cette macro exécute grossièrement :


tar zxf <monarchive.tar.gz>
mv %{name}/* <destdir>

Mon archive contient plusieurs racines

Dans ce cas c’est très simple, vous pouvez utiliser directement la commande suivante :

%setup -q -c

J’ai plusieurs archives de sources

Dans ce cas utilisez l’appel suivant :

%setup -q -a X <autres_params>

X représente ici l’indice derrière l’entrée Source de l’en-tête. Par exemple pour Source1 X vaudra 1 et ainsi de suite.

J’ai plusieurs archives de sources et je veux écraser des fichiers venant des archives précédentes

Tsss, vous en êtes difficiles. :-p Vous pouvez utiliser l’appel suivant :

%setup -q -a X -T -D <autres_params>

Je suis un warrior, je veux tout faire tout seul

OK OK, bon dans ce cas il faut décompresser à la main vos sources et les copier dans le dossier %{_builddir}.

La macro %build

La macro %build sert comme son nom l’indique à compiler les paquets. C’est surtout pratique pour les paquets en C/C++, et dans ce cas son squelette sera souvent :


%configure
make

Cela dit vous pouvez parfaitement la supprimer si vous n’en avez pas besoin, par exemple quand vous devez packager un outil propriétaire dont vous n’avez pas les sources (beurk !).

La section %install

La macro %install sert à construire l’archive finale telle qu’elle sera dans le RPM, autrement dit les chemins de dossier créés dans cette archive seront les chemins finaux du RPM. Un détail toutefois : au niveau de l’archive tous les fichiers créés doivent être enfants du dossier %{buildroot}.

Dans les cas les plus simples, cette macro se résume à :

make install DESTDIR=%{buildroot}

Si maintenant vous devez faire des copies de fichiers ou autres, sachez que le dossier courant des %{_builddir} et que vous copiez vers %{buildroot}. Prenons exemple sur la commande suivante :

cp -ar foo/bar %{buildroot}/foo/bar

Elle signifie : copier le dossier foo/bar de %{_builddir} vers %{buildroot}/foo/bar. Assurez-vous néanmoins que %{buildroot}/foo existe bien, et au besoin créez-le avec un mkdir -p.

Dernier point concernant les liens symboliques : vous les créez dans %{buildroot} mais ils doivent cibler leur destination sans %{buildroot}. Par exemple il faut taper :


ln -sf /usr/bin/python %{buildroot}/usr/local/bin/python

Ce n’est pas grave si la cible du lien n’existe pas au moment de la création du paquet, l’important est qu’elle existe à l’installation du paquet.

La section %clean

Cette section peut être utile, mais globalement son contenu sera toujours le même, à savoir :

rm -rf %{buildroot}/*

Les sections d’installation

Les sections d’installation sont au nombre de quatre. Dans tous les cas ce sont des scripts shell qui ne doivent pas faire appel à des macros.
Voici donc les sections :
- %pre : cette section contient les étapes à faire avant la décompression du paquet. On y trouve par exemple des sanity checks de l’environnement pour des éléments ne dépendant pas de RPM, tels que savoir si tel ou tel utilisateur LDAP existe bien.
- %post : cette section contient les étapes à faire après la décompression du paquet. On y trouve notamment les enregistrements de service système.
- %preun : cette section contient les étapes à faire juste au début de la désinstallation du paquet, avant suppression des fichiers. On y trouvera notamment les arrêts de service système et leur désinscription.
- %postun : cette section contient les étapes à faire juste après la désinstallation du paquet, par exemple des suppressions d’utilisateur si besoin, bien que cet exemple précis soit très risqué et déconseillé.

Le cas des mises à jour de paquets

Lors d’une mise à jour de paquet, le workflow exécuté est le suivant

  1. %pre du nouveau paquet.
  2. Décompression des fichiers du nouveau paquet.
  3. %post du nouveau paquet.
  4. %preun de l’ancien paquet.
  5. Suppression des fichiers de l’ancien paquet qui ne sont plus référencés par le nouveau.
  6. %postun de l’ancien paquet.

Dès lors dans les scripts, pour savoir si on est dans une installation, une suppression ou une mise à jour, RPM passe en paramètre aux scripts un compteur d’installation. Celui-ci vaut 0 dans le cas d’une installation ou désinstallation, et une autre valeur dans le cas d’une mise à jour.

Dès lors, si vous voulez par exemple désinstaller un service uniquement dans le cas d’une déinstallation, vous pouvez utiliser ceci :


if [ $1 == 0 ]
then
   /etc/init.d/myService stop
   /sbin/chkconfig --del myService
fi

Le build du RPM

Attention, ce qui suit ne sert qu’à titre d’exemple et est déconseillé dans la vraie vie pour la simple raison que ça ne garantit pas des builds reproductibles. Pour construire des RPMs proprement il faut passer par Mock.

Et quoi qu’il en soit ne tentez jamais de créer un paquet RPM depuis l’utilisateur root. Outre le fait que vous risquez de casser votre système, ça augmente les risques d’avoir des paquets inutilisables.

Création des répertoires

Créez tout d’abord un dossier ~/rpmbuild. Celui-ci doit ensuite contenir les sous-dossiers suivants :

- SPECS : l’endroit où copier votre fichier .spec.
- SOURCES : l’endroit où copier vos fichiers de sources.
- BUILD : l’emplacement %{_builddir} évoqué plus haut.
- BUILDROOT : l’emplacement %{buildroot} évoqué plus haut.
- SRPMS : l’endroit où récupérer vos RPM sources, à savoir une archive qui contient ce qu’il faut pour refaire un build de vos RPM.
- RPMS : l’endroit où sont créés vos RPMs.

Création de vos RPMs

La dernière étape consiste à créer votre RPM. Pour ce faire copiez votre fichier .spec dans ~/rpmbuild/SPECS, copiez vos archives de sources dans ~/rpmbuild/SOURCES, puis lancez les commandes :


cd ~/rpmbuild
rpmbuild -ba SPECS/<votre_fichier_spec>

Si tout se passe bien vous pourrez récupérer votre RPM dans le dossier ~/rpmbuild/RPMS.

Astuces et autres trucs à savoir

Ici on présente quelques astuces et autres trucs bien utiles.

Commentaires

Les commentaires doivent commencer par un dièse #. Attention néanmoins, si vous référencez une variable ou une macro RPM, veillez bien à supprimer le caractère % au moment de votre commentaire, sinon au moment du build la ligne faisant appel à la macro ou à la variable ne sera pas considérée comme un commentaires.

Et pour rappel laisser du code commenté c’est mal, en particulier si vous utilisez un gestionnaire de version. Ce dernier aide justement à voir les différences entre les différentes versions d’un fichier, et donc le code commenté n’a plus lieu d’être dans ce cas.

Variabilisez vos fichiers

Il est fortement recommandé de variabiliser vos fichiers, au moins pour les chemins. Pour définir une variable, il faut ajouter une ligne comme celle-ci :

%global _myVar /foo/bar

Ensuite pour l’appelez vous utilisez le code suivant :

%{_myVar}

Pour aller plus loin…

Cette documentation n’est certainement pas exhaustive. Jusqu’à il y a peu l’écosystème RPM était extrêmement mal documenté mais ça s’améliore. La documentation la plus complète est sur le wiki de Fedora.

Cet article vous a plu ? Vous aimerez sûrement aussi :

La démarche devops
L’intégration continue
2014 sera Javascript ou ne sera pas
Plus loin avec la création de paquets RPM

Julien

Twitter : @julek42

Envie de tester votre niveau technique ?
Cliquez ci-dessous !

 

JulienJulien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date ;-) ). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.

Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.
En savoir +

Vous souhaitez devenir blogueur pour JobProd ?

Share Button

Répondre