"Kotlin"
https://kotlinlang.org
est un langage développé par JetBrains, l'éditeur d'IntelliJ IDEA.
Kotlin est un langage statiquement typé, compilé soit vers du bytecode
`Java 6` ou Android, soit vers du JavaScript. L'intéropérabilité avec
Java et le support par IDEA sont très poussés. La dernière version est
la `1.0 beta 3`, tout à fait utilisable. Les sources du compilateurs
sont ouvertes et disponibles sous license "Apache 2".
Kotlin intègre de nombreuses facilités syntaxiques ayant fait le
succès de Ruby, Python ou Groovy, mais sans jamais sacrifier le typage
statique. C'est à dire qu'on peut atteindre le même niveau de
concision sans se faire mal à l'exécution. Il va également piquer des
idées chez Scala et `C#`.
Ce qui est impressionnant, c'est qu'au lieu d'un entassement de
fonctionnalités trop spécialisées, Kotlin offre des mécanismes
d'extension simples et élégants, qui se combinent de façon
surprenante.
Les temps de compilation et d'exécution ne diffèrent pas beaucoup de
ceux de Java. Kotlin manque encore de bibliothèques spécialisées, mais
l'intéropérabilité avec Java comble le vide, et permet également de
tester Kotlin sur un morceau non-critique d'un projet déjà développé
en Java. Pour tous ceux qui ont un patrimoine cognitif ou logiciel lié
à Java (y compris pour Android), je recommande vivement de surveiller
l'évolution de Kotlin.
=== Ansible à la loupe
Voici un vrai morceau de Playbook Ansible pour installer le JRE d'Oracle :
<<<
- stat: path='{{ jre_parent_dir }}/{{ jre_archive }}'
register: jre_archive_stat
- file: path='{{ jre_parent_dir }}' state=directory
when: not jre_archive_stat.stat.exists
- command: "wget --no-cookies --no-check-certificate --header
\"Cookie: gpw_e24=http%3A%2F%
2Fwww.oracle.com%2F;
oraclelicense=accept-securebackup-cookie\"
\"
http://download.oracle.com/otn-pub/java/jdk/{{ jre_version_precise
}}/{{ jre_archive }}\""
args:
chdir: '{{ jre_parent_dir }}'
when: not jre_archive_stat.stat.exists
- command: tar -zxvf {{ jre_archive }} chdir='{{ jre_parent_dir }}'
- command: update-alternatives --install /usr/bin/java java {{
jre_home }}/bin/java 100
- command: update-alternatives --config java
- file: path=~/.oracle_jre_usage state=absent
>>>
Ça dit en gros : si le répertoire-cible n'existe pas, on le crée, puis
on télécharge le JRE. Puis on éclate l'archive, et on en fait la
version de Java par défaut.
Pour la suite on laissera tomber la création de l'URL. Ici le YAML est
tout à fait lisible, avec quelques petites irrégularités qui d'abord
ne sautent pas aux yeux. Il faut échapper la valeur YAML si elle
commence par le ``{`` utilisé par les expressions Jinja. Pour accéder
au résultat de la commande ``stat`` qui nous intéresse (l'existence du
fichier), il faut naviguer dans la structure avec un ``.stat.exists``
parce qu'il n'y a pas moyen d'enregistrer un sous-élément du résultat.
Cet enregistrement se fait dans un espace de noms global. Bien sûr, si
on écrit une variable de travers, on est bon pour un problème à
l'exécution.
Que donnerait la même chose, recodée en utilisant les facilités
offertes par Kotlin ?
=== Kable
Kable est une maquette pour voir s'il est possible de développer en
Kotlin l'équivalent des Playbooks Ansible. Il y a moyen de sauter la
visite guidée en allant voir tout de suite le "projet github"
https://github.com/caillette/Kable
.
Déjà, pour se faire une idée, voici déjà une réécriture de l'exemple
précédent avec Kable :
<<<
class JreInstallationPlaybook(
jreArchivesDir : String,
jreHome : String,
jreVersionPrecise : String
) : Playbook( {
val jreArchive = "jre" + jreVersionPrecise
val wgetCommandLine = StringBuilder()
.append( "wget --no-cookies --no-check-certificate " )
.append( "--header \"Cookie: gpw_e24=http%3A%2F%
2Fwww.oracle.com%2F; " )
.append( "oraclelicense=accept-securebackup-cookie\" " )
.append( "\"
http://download.oracle.com/otn-pub/java/jdk/" )
.append( jreVersionPrecise )
.append( "/" )
.append( jreArchive )
.append( "\"" )
.toString()
val jrePresent =
- Stat( file = jreArchivesDir + "/" + jreArchive )
resultTransformedBy { it.exists }
- DoFile( path = jreArchivesDir, state = DoFile.State.DIRECTORY )
unless( jrePresent )
- RunProcess( wgetCommandLine, changeToDirectory = jreArchivesDir)
unless( jrePresent )
- RunProcess( "tar -zxvg $jreArchive", changeToDirectory = jreArchivesDir )
- RunProcess( "update-alternatives --install /usr/bin/java java
$jreHome/bin/java 100" )
- RunProcess( "update-alternatives --config java" )
- DoFile( path = "~/.oracle_jre_usage", state = DoFile.State.ABSENT )
} )
>>>
Kable n'est pas plus verbeux qu'Ansible, même si on utilise des
paramètres nommés partout. Ce qui est complètement fou, c'est que tout
est typé. Même le résultat de ``Stat``, qui s'évalue finalement comme
un ``Boolean``. L'environnement de développement remonte donc toutes
les erreurs. Par exemple, pas possible de passer une chaîne de
caractères à la fonction ``unless``. Par rapport à Ansible, le gain de
productivité est effarant.
=== Plan d'exécution
Avant de découvrir comment Kotlin permet toutes ces merveilles, voyons
comment fonctionne un ``Playbook``. On avait déjà vu dans PalletOps la
notion de plan d'exécution. Dans Kable, le principe est le suivant :
un ``Playbook`` contient une liste de ``Task``. Chaque ``Task``
contient des paramètres d'exécution. L'exécution du programme Kotlin
consiste d'abord à peupler ces listes pour fabriquer
l'``ExecutionPlan``. Pour l'instant Kable n'en fait pas plus, mais un
vrai outil procéderait alors à diverses phases de validation.
Un autre intérêt de l'``ExecutionPlan`` c'est d'indiquer le niveau
d'achèvement d'une exécution. Chaque machine-cible donne lieu à une
exécution en parallèle.
Si on veut que l'exécution des commandes ait lieu après la
construction du plan, il faut que tous les paramètres soit utilisables
en différé. Si ce sont des données immuables, pas de problème. Si on
veut effectuer un branchement conditionnel, alors on ne va pas
mémoriser la valeur, mais une référence à la valeur future, et une
fonction d'évaluation. Kable définit la classe ``Deferred`` pour
capturer cette valeur. Conceptuellement le ``Deferred`` fait la même
chose qu'un ``Future`` en Java, à ceci près qu'il doit centraliser les
valeurs pour plusieurs exécutions simultanées d'un ``Playbook``.
Ça signifie que Kable affecte au moins un fil d'exécution par
connexion à une machine distante, et n'hésite pas à le bloquer. C'est
une pratique déconseillée si on veut maximiser l'utilisation des
ressources système et qu'on a beaucoup de fils d'exécution.
Mais beaucoup, c'est combien ? D'après l'article de Peter Lawrey
"Java: What is the Limit to the Number of Threads You Can Create?"
https://dzone.com/articles/java-what-limit-number-threads
on atteint la dizaine de milliers sans problème. Après, il faut voir
combien de ressources mange le SSH. Aux dernières nouvelles, Ansible
(qui doit fonctionner de la même manière) arrive à orchestrer
plusieurs milliers de serveurs.
Bon, le but n'étaut pas de rentrer dans les détails d'architecture,
mais juste d'avoir en tête certains impératifs techniques avant de
regarder la syntaxe du ``Playbook``.