JBang, Script en java
Contexte
Pour le besoin d’un projet, j’avais besoin d’extraire un certificat au format PEM à partir d’un ensemble de clés JWKS (JSON Web Key Sets). Je savais que je pouvais le faire très facilement en Java. Cependant, créer un projet Java jetable n’est pas très motivant pour simplement quelques lignes de code. D’où l’idée d’écrire un script pour cela.
En effet, depuis le JDK 11, il est possible de réaliser un script en Java (JEP 330: Launch Single-File Source-Code Programs). Malheureusement, cela est limité aux classes fournies par le JDK.
Depuis quelques temps, j’entends parler de jbang. Il permettrait de lever cette contrainte. C’est l’occasion de le mettre à l’épreuve.
Installation
La procédure d’installation est très simple sur la page téléchargement du site du projet. La page décrit les opérations selon les différents systèmes d’exploitation.
Pour ma part, n’ayant pas de package Debian, j’ai choisi la solution manuelle en m’assurant que le programme soit accessible via la variable $PATH.
Bien que je possède plusieurs versions de JVM, il est intéressant de savoir que l’installation du JDK au préalable n’est pas nécessaire.
Premier lancement
L’outil me propose d’initialiser le script comme il faut avec l’option init. Laissons-nous guider pour commencer.
jbang init Hello.java
Un fichier Hello.java est généré. C’est l’occasion de voir son anatomie.
///usr/bin/env jbang "$0" "$@" ; exit $?
// //DEPS <dependency1> <dependency2>
import static java.lang.System.*;
public class Hello {
public static void main(String... args) {
out.println("Hello World");
}
}
La première ligne est le fameux shebang. C’est l’information qui permet de savoir quelle programme doit être exécuté.
L’usage du style // au lieu du classique #! est une astuce (bash, zsh, etc…) qui permet l’exécution comme un script tout en ayant un code Java valide.
Sur la deuxième ligne, nous aurons l’occasion de revenir dessus.
Le reste, c’est du code Java classique. Une classe Hello
ayant une méthode main
.
Passons à l’exécution, il suffit de taper la commande suivante :
jbang Hello.java
Nous obtenons l’affichage suivant :
[jbang] Building jar...
Hello World
Grâce au Shebang, nous pouvons aussi appeler directement le script
./Hello.java
Si besoin, il faut donner les droits d’exécution avec la commande suivante : chmod u+x Hello.java
.
Nous obtenons l’affichage suivant :
Hello World
Le script n’a pas été modifié. Donc, il n’a pas été nécessaire de reconstruire l’archive.
Patron ("template")
Il est possible d’utiliser des patrons. Un certain nombre existe déjà comme la possibilité d’avoir ce qui faut pour écrire un outil en ligne de commande.
jbang init --template=cli Cli.java
ou en utilisant les options courtes :
jbang init -t cli Cli.java
De là, nous pouvons exécuter directement le script
./Cli.sh
[jbang] Building jar...
Hello World!
Cela n’a rien d’étonnant par rapport à tout à l’heure. Sauf que comme tout outil en ligne de commande qui se respecte, nous avons une aide à notre disposition sans effort de notre part.
./Cli.java --help
Usage: Cli [-hV] <greeting>
Cli made with jbang
<greeting> The greeting to print
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Je vois que j’ai un argument qui permet de préciser la salutation. De ce pas, j’exécute la commande suivante :
./Cli.java Lilian
Hello Lilian
Passons au code,
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.5.0
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import java.util.concurrent.Callable;
@Command(name = "Cli", mixinStandardHelpOptions = true, version = "Cli 0.1",
description = "Cli made with jbang")
class Cli implements Callable<Integer> {
@Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
private String greeting;
public static void main(String... args) {
int exitCode = new CommandLine(new Cli()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() throws Exception { // your business logic goes here...
System.out.println("Hello " + greeting);
return 0;
}
}
Nous voyons au niveau du code, que c’est la librairie picocli qui est utilisée afin de simplifier les gestions des arguments.
Mais le plus intéressant, c’est la seconde ligne.
//DEPS info.picocli:picocli:4.5.0
Par cette ligne, nous exprimons le fait que le script a besoin de la librairie picocli. Nous retrouvons le schéma classique groupId:artifactId:version.
Mise en pratique
J’initialise mon script
jbang init -t cli GetKey.java
Je précise la librairie que je souhaite utiliser, en l’occurrence jose4j
//DEPS org.bitbucket.b_c:jose4j:0.7.6
Je suis un aficionados de vi. Mais effectivement, cela n’est pas top pour le développement Java par rapport à un IDE.(Gestion des imports, autocomplétion, validation syntaxique, etc..)
Là encore, jbang
vient à notre rescousse en proposant un mode édition.
jbang edit --open=vscode GetKey.java
Votre IDE s’ouvre :
Je précise mon paramètre de ligne de commande :
@Parameters(index = "0", description = "URL vers JSON Web Key Sets (JWKS)")
private String url;
J’écris le code pour récupérer le certificat et l’afficher au format PEM
@Override
public Integer call() throws Exception {
HttpsJwks jwks = new HttpsJwks(url);
List<JsonWebKey> liste = jwks.getJsonWebKeys();
for (JsonWebKey jsonWebKey : liste) {
String cert = RsaKeyUtil.pemEncode(jsonWebKey.getPublicKey());
System.out.println(cert);
}
return 0;
}
il me reste plus qu’à l’éxecuter
./GetKey https://MON.SITE/auth/realms/OMS-Intra/protocol/openid-connect/certs
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlWybNYyj0hxoE8AcpGT
...
...
oU1MWOIhL/9ex7UB+6C3Uj2Mg3uoHQZB2PaeHHTb0fm957ufzWHGUyFd0q3UjUTQ
2N7mvALKLPsakuppHzRMeXHt7Zhu8kUfhPhQDUYc+l5bB1HhUqvZ9rjVTDRP7R+T
uQIDAQAB
-----END PUBLIC KEY-----
Et voilà, le tour est joué. A votre tour !
Moteur de recherche
"Eduquer, ce n'est pas remplir des vases mais c'est d'allumer des feux." - Michel Montaigne
Billets récents
- Eclipse plante systématiquement sous Debian (et autres distribution Linux)
- JEP 463, Implicitly Declared Classes and Instance Main Methods (Second Preview)
- Debian - Montée de version de Debian 11 (Bullseye) à Debian 12 (Bookworm)
- JEP 451, Prepare to Disallow the Dynamic Loading of Agents
- JEP 444, Virtual Threads