Publier dans OSSRH (Maven Central)

Vous avez un projet open source qui produit des Jars une fois packagé. Le réflexe dans ce cas est souvent de se tourner vers OSSRH pour partager son projet à grande échelle (en plus Github, Gitlab ou équivalent).

OSSRH (pour Open Source Software Repository Hosting) représente le repo Maven souvent référencé sous le nom Maven Central et géré par la société Sonatype. C'est lui qui contient une grande majorité des binaires des projets open sources, que nous retrouvons lorsque nous récupérons des dépendances à travers Maven, Gradle ou SBT. OSSRH est basé sur gestionnaire de repo Nexus, développé lui aussi pas Nexus.

Nous allons voir dans cet article un modus operandi possible pour transmettre votre livrable sur Maven Central, surtout si c'est la première fois que vous le fait !

Phase 1 : Se faire référencer auprès de Sonatype

Si votre projet est nouveau, si le group ID associé à votre projet n'existe pas encore et si vous n'êtes pas encore référencé auprès de Sonatype, il va falloir que vous alliez faire un tour sur leur Jira.

  • C'est votre première fois avec Sonatype : créez un nouveau compte pour vous dans le Jira de Sonatype.
  • Le group ID sous lequel vous souhaitez publier n'existe pas : créez un ticket "New Project" dans le Jira sous le projet Community Support - Open Source Project Repository Hosting. Pensez à indiquer que vous être propriétaire du domaine liée à votre group ID.
  • Le group ID existe, mais vous ne pouvez pas livrer dessus : créez une "Task" dans le Jira sous le projet Community Support - Open Source Project Repository Hosting. Selon cas, pensez à indiquer que vous être copropriétaire ou le nouveau propriétaire du domaine liée à votre group ID. Si vous l'avez, donnez la référence de l'ancien ticket.

La création de compte est immédiate. Par contre, la gestion des tickets Jira est processus faisant intervenir des humains. Il faut compter quelques heures avant une intervention.

Phase 2 : PGP

PGP est l'acronyme de Pretty Good Privacy. C'est un outils dont le but est de réaliser différentes tâches sur la base de chiffrement cryptographique. Il permet notamment d’apposer des signatures à du contenu électronique. PGP se base sur le standard OpenPGP, définit par l'IETF dans le RFC 4880. Un autre outil se base sur OpenPGP. Il s'agit de GnuPG (pour Gnu Privacy Guard), que nous allons utiliser ici.

PGP sert dans le cadre de la publication sous OSSRH à associer une signature aux éléments à livrer. De son côté, OSSRH utilise la signature pour la vérifier au près d'un serveur de clés.

En utilisant GnuPG (que vous devrez avoir préalablement installé), la génération de la clé passe par la commande gpg et l'option --generate-key. Il existe à partir de là, plusieurs façon de renseigner les informations pour générer la clé. La méthode la plus limpide à mon goût consiste à créer un fichier script GnuPG.

# File: gpg-add-me
%echo Generate key for François Sarradin @ MyDomain

Key-Type: RSA
Key-Length: 4096

Name-Real: François Sarradin
Name-Email: francois@my-domain.dtc
Name-Comment: François Sarradin @ MyDomain
Expire-Date: 0

%commit
%echo done

Un champ a volontairement été oublié dans ce fichier : il s'agit du champ Passphrase. J'ai personnellement avec les mots de passe en clair dans un fichier. Mais il est possible de fournir autrement la passphrase pour la génération de la clé :

  • --passphrase qui permet de préciser le mot de passe en paramètre de gpg.
  • --passphrase-file qui va chercher la passphrase dans la première ligne d'un fichier.
  • --passphrase-fd qui va chercher la passphrase à travers un descripteur de fichier, sachant que 0 représente /dev/stdin.

Cependant, même avec ces options, nous restons dans un cadre où votre passphrase apparaît en clair quelque part, dans un fichier ou sur votre terminal. Par éviter ça, je vous propose le script suivant, qui récupère votre passphrase sans l'afficher avec la commande read -s et la transmet à gpg.

$ echo "Passphrase:";\
read -s pass;\
gpg --batch --pinentry-mode loopback --passphrase "$pass" --generate-key gpg-add-me;\
unset pass

Votre clé est générée. Pour vous en convaincre, faites un gpg --list-key.

/Users/francois/.gnupg/pubring.kbx
-----------------------------------
pub   rsa4096 2001-08-08 [SCEA]
      123456789006CD7C3B15F3FA7FFE6D66DC4801E2
uid          [  ultime ] François Sarradin (François Sarradin @ MyDomain) <francois@my-domain.dtc>

Vous avez donc à présent une clé qui vous permettra de signer votre livrable. Mais il vous faut aussi la rendre accessible à Sonatype en l'envoyant à un serveur de clé accessible à travers le monde. Nous utiliser à nouveau gpg pour ça.

$ gpg --keyserver hkp://pool.sks-keyservers.net --send-keys 123456789006CD7C3B15F3FA7FFE6D66DC4801E2

Il faut un moment pour que l'ensemble des serveurs se synchronisent (comptez un quart d'heure). Si vous souhaitez vérifier que votre clé a bien été enregistrée, utilisez la commande suivante :

$ gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 123456789006CD7C3B15F3FA7FFE6D66DC4801E2

Testez aussi directement sur le serveur Web mis à disposition : pool.sks-keyservers.net:11371. Fournissez par exemple votre adresse email ou votre clé (en préfixant avec 0x).

À ce niveau, vous en avez fini avec PGP ! Pour l'ensemble de cette phase, il faut compter une bonne demi heure.

Phase 3 : Signez vos livrables !

Sous SBT (parce qu'à Univalence, on fait surtout du Scala !), il vous faudra utiliser le plugin sbt-pgp. Ajouter cette ligne dans votre fichier [project-root]\project\plugins.sbt.

addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1")

Par défaut, sbt-pgp se base sur Bouncy Castle. Bouncy Castle est une bibliothèque implémentant OpenPGP et donc une alternative GnuPG, ce qui ne nous intéresse pas ici. Il faut indiquer à sbt-pgp d'utiliser GnuPG. Pour cela, il suffit d'ajouter la propriété useGpg := true dans le fichier build.sbt (avec un exemple sur l'un de nos projets).

Vous pouvez alors tester en local la signature de votre livrable en utilisant la commande :

$ sbt publishLocalSigned

Le livrable est alors disponible dans le répertoire ~/.ivy2/local/.

Cette étape n'est pas très longue : elle est de l'ordre de la dizaine de minute, selon le temps que prend la compilation, les tests et le packaging.

Phase 4 : Vérifier les informations liées à votre projet

Un certain nombre d'informations sont requises lorsque vous voulez livrer sur OSSRH (avec le nom des propriétés entre parenthèses) :

  • Le groupID (organization) et l'artifactID (name), ainsi qu'un numéro de version (version).
  • Une description (description) et l'URL représentation soit le site Web du projet ou l'emplacement du code (sur Github, Gitlab, BitBucket...) (homepage), la date de démarrage du projet (startYear).
  • Les licences couvrant le projet (licenses, sous la forme d'une liste de couples nom de la licence / URL du texte de la licence).
  • Les développeurs ayant participé au projet (developers, qui est une liste d'entité de type Developer).
  • Des informations sur le repo utilisé pour la gestion du code source (scmInfo, qui est une Option sur l'entité ScmInfo).

Il vous faut aussi ajouter des informations qui seront utilisées par Sonatype. Pour cela, nous allons utiliser le plugin SBT sbt-sonatype. Nous rajoutons alors cette ligne dans [project-root]/project/plugins.sbt :

addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")

Il faut alors juste redéfinir publishTo.

lazy val publishSettings =
  Def.settings(
    publishTo := sonatypePublishTo.value,
    useGpg := true
  )

Derrière, sbt-sonatype redéfini un certain nombre de valeurs, dont :

  • sonatypeProfileName qui est positionné sur organization.
  • publishMavenStyle positionné à true.
  • sonatypePublishTo qui selon la valeur de isSnapshot est positionné sur le repo de snapshot de Sonatype ou sur son repo de staging. La valeur de isSnapshot est positionnée à true si le numéro de version est suffixé par "-SNAPSHOT". Sinon, elle vaut false.
  • credentials contenant les informations d'authentification, positionnée à partir des variables d'environnements SONATYPE_USERNAME et SONATYPE_PASSWORD.

Pour credentials se pose à niveau la question de positionner la variable d'environnement SONATYPE_PASSWORD sans avoir à afficher ou à stocker en clair son contenu. Tout d'abord, il faut savoir que la plupart des services de CI (Continuous Integration) permettent de définir pour des jobs spécifiques des variables d'environnement dont la valeur associée est cachée. C'est le cas de Travis CI.

Sinon, pour le fait de fournir la valeur de SONATYPE_PASSWORD depuis son poste de travail, il n'y a pas de solution efficace, à part peut-être créer une nouvelle tâche dans SBT.

Nous sommes à nouveau autour de la dizaine de minute sur cette phase.

Phase 5 : Livrez !

Le type de livraison dans OSSRH est automatiquement dépendant du format de votre numéro de version. Si votre numéro de version se termine par "-SNAPSHOT", c'est le repo Snapshot d'OSSRH qui sera visé. Sinon, ce sera le repo de Staging.

Le repo Snapshot est un repo dans lequel vous pouvez livrer plusieurs fois la même version de votre livrable. Il est souvent destiné au test d'un livrable, dont la stabilité n'est pas considérée comme suffisante.

Par contre, si vous voulez publier une version finale de votre livrable, il vous faudra passer par le repo de Staging avant le repo de Release. Le repo de Staging permet à la plateforme OSSRH de mettre en place un processus incluant la vérifications de la présence du code source et de la javadoc, de la signature, de métadonnées contenues. Cette étape peut prendre plusieurs minutes.

Une fois cette étape terminée, vous pouvez transmettre votre livrable au repo de Release. Et une fois sur Release, il faut compter une dizaine de minutes pour pouvoir retrouver votre livrable dans search.maven.org.

Ceci dit, si c'est la première fois que vous livrez sous un group ID, il vous faudra créer une tâche Jira pour avertir les équipes de Sonatype, afin qu'ils mettent en place le système de synchronisation entre Nexus et Maven Central pour votre group ID. Cette étape est normalement faite une fois pour toute. Mais il faut tout de même compter quelques heures pour son déroulement.

L'ensemble des processus décrits ci-dessus est couvert par les tâches fournies par le plugin sbt-sonatype. La livraison en Snapshot ou en Staging se fait par la tâche SBT publishSigned. Et le déploiement de Staging à Release se fait à travers la tâche sonatypeReleaseAll.

Conclusion

Estimation de durée des différentes phases

Phase Objectif ETA (en heure)
Phase 1 : Se faire référencer auprès de Sonatype Création de compte et du groupID 3
Phase 2 : PGP Créer une clé de signature et la référencer 0.5
Phase 3 : Signez vos livrables ! Être conforme aux attentes de Sonatype 0.25
Phase 4 : Vérifier les informations liées à votre projet Être conforme aux attentes de Sonatype 0.25
Phase 5 : Livrez ! Publiez le livrable sur Maven Central 3h

Donc, lorsque vous livrez sous un nouveau groupID, vous devez compter environ 7 h pour atteindre Maven Central. Mais, ce temps n'intègre pas les erreurs de manipulations et le décalage horaire entre vous et les équipes de Sonatype. Pour être plus réaliste, il vous faut compter 2 à 3 jours pour un parcours complet.

Par contre, si le parcours est déjà initié, la livraison peut être réduite en temps, sachant que certaines phases et certaines étapes ne sont plus nécessaires : comme les phases de 1 à 4 ainsi que la demande synchronisation dans la phase 5. Il ne reste que la publication signée dans Staging, les vérifications dans Staging, la promotion dans Release et la synchronisation avec Maven Central. Ces étapes sont automatisables (grâce à sbt-sonatype) ou déjà automatisés et ont une durée de l'ordre de l'heure.