$30 off During Our Annual Pro Sale. View Details »

Automatisez vos workflows en Kotlin

mbonnin
October 19, 2019

Automatisez vos workflows en Kotlin

Ce talk est un retour d'expérience sur la manière dont nous utilisons kotlin à toutes les étapes du développement de notre app Android chez Dailymotion:

* sur les machines de dev, pour la gestion des pull requests, connection jira et déploiement avec un outil en ligne de commande utilisant Clikt.

* sur les serveurs de CI avec un runner kotlin qui execute la compilation, tests et envoi des artifacts.

* sur un servlet Google Cloud pour héberger nos artifacts et Kdoc.

* sur un cron pour monitorer les commentaires utilisateurs du Google Play.

* enfin, de manière générale, pour remplacer bash et perl par du kotlin partout grâce à kscript!

Le but du talk n'est pas d'aller dans le détail de chaque technologie mais plus de montrer l'étendue des possibles. L'écosystème kotlin rend accessible un grand nombre de tâches.

mbonnin

October 19, 2019
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. Automatisez vos workflows en Kotlin

  2. @MartinBonnin 2

  3. Dailymotion 3 • Sprints 2 semaines • Release à la

    fin de chaque sprint • Feature flags • Product Owner, Designers • Jira partout!
  4. Environment 4 Jira Github Slack Bitrise Transifex Play console Appcenter

  5. Workflows (simplifiés) 5 1. S’assigner un ticket jira 2. Ouvrir

    une branche github 3. Coder... 4. Créér une pull request 5. Déplacer le ticket 6. Merger la pull request 7. Déplacer le ticket 8. Créér une alpha ◦ Incrémenter version ◦ Tag ◦ Push 9. Envoyer un message aux designers/product owners 10. Intégrer les retours 11. Revenir à l’étape 1. • Envoyer les nouvelles chaines de caractères • Récupérer les traductions • Vérifier les traductions • Mettre à jour le changelog • Notifier la nouvelle version sur jira • Envoyer en beta • Mettre le changelog sur le play store. • Démarrer le rollout • Et aussi sur Amazon... ➡ Et tout nettoyer à la fin
  6. Tout Automatiser !! 6

  7. Automation: https://xkcd.com/1319/ Aussi: https://xkcd.com/612/ 7

  8. Avantages 8 • Reproductibilité • Fiabilité • Auto-documentation • Bon

    pour le moral • Gain de temps (des fois)
  9. Solutions existantes 9 • Hub ◦ go • Play-publisher ◦

    gradle plugin • Client transifex ◦ python • Fastlane ◦ Ruby • Bitrise cli ◦ go
  10. 10 Syndrome /bin/bash #!/bin/bash my_array=(item0 item1 "item2") # guillemets ou

    pas guillemets ? unset my_array[1] echo ${my_array[@]} # index '@' ? my_array += "item 3" # les espaces !! if [ ${#ArrayName[@]} = 0 ] # or is it if [[ "${#ArrayName[@]}" == 0]] ? then # ne pas oublier ! echo "yeaaa, j'aime bash <3" fi
  11. Changements de contexte 11 • Réapprendre la syntaxe • Réapprendre

    les outils • Réapprendre les bibliothèques ◦ Comment faire une requête HTTP ⬦ curl ⬦ urllib ⬦ net/http Perte de productivité & Frustration
  12. Tout automatiser en Kotlin 12

  13. Kotlin 13 • Pas de changement de contexte • Un

    IDE au top (la plupart du temps!) • Un écosystème riche
  14. Boite à outils 14 • Outils ◦ Clikt (cli) ◦

    Kscript (~bash) ◦ Servlets (cloud) • Bibliothèques ◦ Gradle Tooling ◦ Moshi ◦ OkHttp ◦ Ktor/NanoHTTPd ◦ … ➡ Exemple d’application à Dailymotion
  15. Clikt 15

  16. Clikt 16 • Interpreteur ligne de commande • 100% Kotlin,

    bientot multiplatforme • Parse les arguments • Il existe aussi kotlinx.cli (1.3.60)
  17. Arguments 17 • Ils peuvent etre optionels ou non. •

    Entier, flottant ou chaine de caractere. • Avoir un nom ou pas. • Dans quel ordre ? ◦ release rollout=0.5 changelog=whatsNew app-release.aab ◦ release app-release.aab rollout=0.5 app
  18. Clickt: Hello world 18 • Un programme qui dit “Bonjour”

    un certain nombre de fois. • 2 parametres: ◦ Count ⬦ Entier (facultatif, 1 par défaut) ◦ Name ⬦ Chaine de caractères à rentrer par l’utilisateur (obligatoire)
  19. 19 class Hello : CliktCommand() { val count: Int by

    option(help = "Nombre de fois") .int().default(1) val name: String by option(help = "Votre nom") .prompt("Qui êtes vous?") override fun run() { for (i in 1..count) { echo("Hello $name!") } } } fun main(args: Array<String>) = Hello().main(args) Hello world
  20. 20 $ ./hello --help Usage: hello [OPTIONS] Options: --count INT

    Nombre de fois --name TEXT Votre nom -h, --help Show this message and exit $ ./hello --count=3 Votre nom: KotlinEverywhere Hello KotlinEverywhere! Hello KotlinEverywhere! Hello KotlinEverywhere! $ ./hello --whoops Usage: hello [OPTIONS] Error: no such option: "--whoops". Hello world
  21. Plugin gradle “application” 21 • apply plugin: 'application' • Crée

    un jar • Spécifie la classe ‘Main’ • Génère des scripts de démarrage
  22. 22 plugins { application kotlin("jvm") version "1.3.31" } application {

    mainClassName = "com.dailymotion.kinta.MainKt" } # you can also create a fat jar if needed tasks.withType<Jar> { manifest { attributes("Main-Class" to "com.dailymotion.kinta.MainKt") } from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) } Plugin Application
  23. Chez dailymotion: Kinta 23 • Le couteau suisse en ligne

    de commande • 26 commandes pour la release, le test, la publication, la maintenance, etc.. • S’execute sur la machine de dev et/ou sur la ci
  24. Construction d’une version: buildTag 24 • buildTag flavorType=alpha buildType=debug 14500

    ◦ Génère un aab signé + universal apk + alphas ◦ Exécute tous les test/vérifications ◦ Envoie sur appcenter
  25. 25 class BuildTag : CliktCommand(name = "buildTag", help = "Makes

    all tests and builds a release.") { val tag by argument(name = "tag", help = "The tag to build").long() val flavorType by option(help = "The flavor to build. Will build all flavor if not specified.") .choice("alpha", "playStore") val buildType by option(help = "The buildType to build. Will build all buildTypes if not specified.") .choice("debug", "release") override fun run() { TagBuild(Gradle(), tag, flavorType, buildType) .execute() } } fun main(args: Array<String>) { BuildTag().main(args) } La commande BuildTag
  26. 26 Bibliothèque: Gradle Tooling API • Execution de taches gradle

    • Redirection de l’entrée standard • Récupération des résultats de chaque tache et de ses dépendances
  27. 27 class Gradle { fun executeTasks(vararg tasks: String) { val

    gradleConnector = GradleConnector.newConnector().forProjectDirectory(File("." )) val connection = gradleConnector.connect() val result = connection.newBuild().forTasks(*tasks) .withArguments("--stacktrace") .run() connection.close() } } Bibliothèque: Gradle Tooling API
  28. 28 Bibliothèque: OkHttp • Le client http par défaut sur

    Android • Développé par square • 100% kotlin depuis la version 4 • API complète • Bientôt multiplateforme ?
  29. 29 //build.gradle implementation("com.squareup.okhttp3:okhttp:4.2.1") //AppCenter.kt val request = Request.Builder() .header("Content-Type", "application/json")

    .header("Accept", "application/json") .header("X-API-Token", appCenterToken) .post(RequestBody.create(null, ByteArray(0))) .url("https://api.appcenter.ms/v0.1/apps/” + ”Dailymotion-Organization/$appId/release_uploa ds") .build() val response = okHttpClient.newCall(request).execute() Bibliothèque: OkHttp
  30. 30 Bibliothèque: Moshi • Un parser json conçu pour kotlin

    • Développé par square • Annotation processor pour génération des parsers • Ou reflexion
  31. 31 //build.gradle implementation("com.squareup.moshi:moshi:1.8.0") //AppCenter.kt @JsonClass(generateAdapter = true) class ReleaseData(val release_url:

    String) val moshi = Moshi.Builder().build() val releaseData = moshi.adapter(ReleaseData::class.java).fromJson(response) // Also without annotation processing val releaseData = moshi.adapter(Any::class.java).fromJson(response) (releaseData as? Map<String, Any>) Bibliothèque: Moshi
  32. 32 Autres bibliothèques • Retrofit • Gson • Kotlinx.serialization •

    Jgit • ... • Et aussi ProcessBuilder
  33. • Création des artifacts avec Gradle Tooling • Envoi sur

    AppCenter avec OkHttp • Récupération du lien dans la réponse avec Moshi • Envoi du lien sur slack avec OkHttp 33 Au final
  34. 34 martin@n550~/git/kinta$ ./bin/buildTag -h Usage: buildTag [OPTIONS] tag Makes verifications,

    builds a aab/apk and uploads to appcenter. Options: --flavor-type [demo|full] Filter the build targets on a particular flavor type --build-type [debug|release] Filter the build targets on a particular build type -h, --help Show this message and exit Arguments: tag The tag to process martin@n550~/git/kinta$ ./bin/buildTag --flavor-type=demo 14500 Compiling version 14500… Running lint… Running unit tests… Running connected tests… Generating universal apk… Uploading… Done. Execution
  35. Récuperation des traductions: txPR 35 • txPR ◦ Récupère les

    traductions dans transifex ◦ Ouvre une pull request avec les nouvelles traductions
  36. 36 class TxPR: CliktCommand(name = "txPR", help = "Pulls the

    latest translations from transifex and open a Pull Request") { override fun run() { createTransifexPR() } } fun main(args: Array<String>) { TxPR().main(args) } Commande txPR
  37. Authentification Github 37 • Authentification du developpeur ◦ Kinta est

    un client Oauth • Oauth ◦ https://github.com/login/oauth/authorize ◦ Login de l’utilisateur ◦ Redirection: https://localhost:666/callback?code=$code ◦ Echange du code contre un token • Ouverture d’un Navigateur ◦ xdg-open/open • WebServer: NanoHttpD/Ktor
  38. 38 Oauth

  39. 39 Bibliothèque: NanoHTTPD • Serveur HTTP léger pour JVM •

    Simple • Remplacer par ktor ?
  40. 40 private fun acquireOauthToken(): String { val url = "https://github.com/login/oauth/authorize?client_id=XYZ&scope=repo&state=$state"

    var token: String? = null Log.d("acquiring oauth token") val server = object : NanoHTTPD(666) { override fun serve(session: IHTTPSession): Response { val code = session.parms["code"] val token = exchangeCode(code, state) synchronized(lock) { token = json.getString("access_token") lock.notify() } return newFixedLengthResponse("yay, you've been authorized !") } } server.start() openBrowser(url) synchronized(lock) { while (token == null) { lock.wait(1000) } } server.stop() return token!! } Bibliothèque: NanoHTTPD
  41. Ouvrir une Pull Request en GraphQL 41 • GraphQL ◦

    Language fortement typé de description d’API ◦ Le client requete uniquement les données dont il a besoin • Github GraphQL api ◦ https://developer.github.com/v4/explorer/
  42. 42 Bibliothèque: Apollo-android • Client GrapqhQL • Fonctionne aussi sans

    Android • Plugin gradle • Génère les modèles automatiquement • Vérification du type à la compilation
  43. 43 # src/main/graphql/com/dailymotion/kinta/github.graphql mutation($input: CreatePullRequestInput!) { createPullRequest(input: $input) { pullRequest

    { number } } } // src/main/kotlin/com/dailymotion/TxPR.kt // Apollo-Android will generate the models automatically val input = CreatePullRequestInput( repositoryId = "MDEwOlJlcG9zaXRvcnkyMTI4NzY5NjA=", baseRefName = "master", headRefName = "feature", title = "Pull request automatique!" ) val response = apolloClient.mutate(CreatePullRequestMutation(input)).toDeferred().aw ait() val prNumber = response.data().pullRequest.number Bibliothèque: Apollo-android
  44. • Récupération des traductions transifex avec OkHttp • Commit des

    nouvelles traductions avec Jgit • Authentification Oauth sur github • Création de la pull request avec Apollo-android 44 Au final
  45. 45 class BuildTag : CliktCommand(name = "buildTag", help = "Makes

    verifications, builds a aab/apk and uploads to appcenter.") { [skip] } class TxPR: CliktCommand(name = "txPR", help = "Pulls the latest translations from transifex and open a Pull Request") { [skip] } class MainCommand: CliktCommand(name = "kinta") { override fun run() { } } fun main(args: Array<String>) { MainCommand() .subcommands(BuildTag(), TxPR()) .main(args) } Sous-commandes
  46. 46 $ kinta Usage: kinta [OPTIONS] COMMAND [ARGS]... Options: -h,

    --help Show this message and exit Commands: BuildTag txPR $ kinta buildTag Usage: kinta buildTag [OPTIONS] tag Error: Missing argument "tag". Sous-commandes
  47. 47 martin@n550~/git/kinta$ _KINTA_COMPLETE=bash kinta > ~/.kinta.completion martin@n550~/git/kinta$ source ~/.kinta.completion martin@n550~/git/kinta$

    kinta <TAB> buildTag txPR BONUS: Bash Autocompletion
  48. Chez dailymotion 48 • startWork • PR • txPull •

    txPush • txPR • nightly • branch • hotfix • uploadWhatsNew • uploadListing • uploadScreenshots • generateScreenshots • trigger • buildPR • buildTag • beta • release • cleanLocal • cleanRemote ➡ S’execute aussi sur la CI
  49. Kscript 49

  50. Kscript 50 • Compilation à la volée de scripts kotlin

    • ~scripts bash ou perl ◦ kscript hello.kts • Meme sans fichier: ◦ kscript 'println("hello world")' • Support de IntelliJ • Support des dépendances
  51. 51 curl -s "https://get.sdkman.io" | bash # install sdkman source

    ~/.bash_profile # add sdkman to PATH sdk install kotlin # install Kotlin sdk install kscript # install Kscript touch hello.kts kscript --idea hello.kts # start the IDE Installation
  52. 52 Hello World!

  53. 53 martin@n550:~/dev$ chmod +x hello.kts martin@n550:~/dev$ ./hello.kts Hello from Kotlin!

    Shebang • #!/usr/bin/env kscript • La première ligne permet de spécifier l’interpreteur • Rend le fichier executable
  54. 54 Debugger

  55. 55 Dépendences @file:DependsOn(“com.squareup. okhttp3:okhttp:4.2.1”)

  56. 56 Example: Ktsify • Migration groovy ➡ Kotlin DSL •

    Process simple mais répétitif: ◦ build.gradle ➡ build.gradle.kts ◦ ‘ ➡ “ ◦ implementation “dep” ➡ implementation(“dep”) ◦ apply plugin: plugin ➡ apply(plugin = plugin) ◦ Etc… • 15 modules • Automatisation avec des regex • Pas de mauvais Ctrl-C Ctrl-V!
  57. 57 Exemples d’utilisation • Envoi des videos d’Android Makers •

    Utilisation de l’api Youtube • Automatisation de la description et des metadonnées • OkHttp • Moshi
  58. Clikt vs KScript 58 • Clikt ◦ Un peu de

    mise en place ◦ Projets complexes ◦ Gradle magic ✨ • KScript ◦ Installation rapide ◦ Pas de generation de code ⬦ Pas de apollo ni moshi-codegen
  59. Servlets 59

  60. Servlet 60 • API pour serveur web • Peut tourner

    en local ou dans le cloud • Se distribue comme fichier WAR • Permet de déployer du code dans le cloud facilement • Google AppEngine supporte les servlets
  61. 61 API Servlet class MainServlet : HttpServlet() { override fun

    service(req: HttpServletRequest, resp: HttpServletResponse) { System.out.println("pathInfo=${req.pathInfo}") resp.status = 200 resp.writer.write("Hello World.") } }
  62. 62 apply plugin: 'com.google.cloud.tools.appengine-standard' apply plugin: 'war' appengine { deploy

    { projectId = 'your-project-id' stopPreviousVersion = true promote = true } } AppEngine Plugin
  63. 63 <!--WEB-INF/web.xml--> <?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>main</servlet-name> <servlet-class>net.mbonnin.appengine.MainServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>main</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> web.xml
  64. Executer le serveur 64 • Créér le projet AppEngine sur

    la console(!) • Executer localement ◦ ./gradlew appengineRun • Déployer sur appengine ◦ ./gradlew appengineDeploy
  65. Example: monitoring des avis sur l’app 65 • Cron executé

    toutes les 15 minutes sur l’url /cron • Récupération des reviews avec les API Google/Apple • Stack habituelle: moshi, kotlin, etc... • Traduction avec l’API Google Translate • Envoi sur slack
  66. 66 Monitoring

  67. Conclusion 67 • Les 3 outils sont complémentaires • IDE

    complet • Cli, Script, Cloud, on peut tout faire en kotlin ◦ Meme des sites web!
  68. Merci! Questions? @MartinBonnin 68

  69. Resources 69 • https://github.com/ajalt/clikt • https://github.com/holgerbrandl/kscript • https://github.com/martinbonnin/ktsify • https://github.com/paug/video-tools/blob/ma

    ster/kscripts/am_youtube_tool.kts • https://github.com/martinbonnin/AppReview sToSlack/blob/master/build.gradle
  70. Credits • Template from SlidesCarnival • Spider from Vecteezy •

    Cat dream by Damian R • Keyboard cat by Cassandra Leigh Gotto 70
  71. DEMO 71