Public | Automated Build

Last pushed: 3 months ago
Short Description
REST API on top of mozilla/readability
Full Description

Readability REST API

Proof of concept of a REST API on top of https://github.com/mozilla/readability

Server

Local

npm install
npm start

Docker

docker build -t readability-api .
docker run -p 4567:4567 readability-api

Usage

You can query any path of the app, just pass an url query parameter with the url encoded to transform into an article.

curl localhost:4567/\?url=http%3A%2F%2Fblog.luxifer.fr%2F2016%2F04%2F04%2Fdeploiement-avec-go-et-ansible-dans-docker%2F

You will get a JSON object like this:

{
   "textContent" : "\n  \n    La semaine dernière je me suis mis en tête de développer une application afin de centraliser tous les déploiements qu’on fait à Xotelia. J’ai fait le tour et il existe en SaaS ou en Open source des solutions pour ça. Je n’en ai pas trouvé qui fonctionne avec ansible (bien que lors d’un déploiement on puisse installer ce qu’on veut), ou alors trop cher. De plus on ne déploie pas chaque branche, seulement master ou une feature-branch qu’on veut tester sur un environnement différent de la production. C’est la que j’ai décidé de développer notre propre outil de déploiement. À savoir que sur chacun de nos projets nous avons un playbook ansible de déploiement, mais que jusqu’à maintenant on les lance sur nos propres machines. Il me fallait aussi un outil qui me permette de lancer une commande dans hipchat pour déclencher un déploiement.\n\nÉtant un grand fan de go et de docker je me suis dit que ça serait pas mal de pouvoir lancer un container docker, dans lequel je lance ansible, et avoir par dessus un petit frontent avec un flux des logs.\n\nJ’ai donc commencé a chercher un client docker en go et je suis tombé sur celui-la. Il me semblait qu’il n’y en avait pas à part dans le core de docker ou alors développé par des tiers, mais je me suis trompé. Et qui plus est, très bien documenté et idiomatic.\n\nPour le stockage des résultats des builds je me suis tourné vers RethinkDB. Une base de données NoSQL.\n\nLe principe de fonctionnement est simple, quand je reçois un événement de déploiement depuis github, je crée un container à partir d’une image qui contient ansible. Dans ce container je clone le dépôt à déployer et je lance le playbook ansible. Je notifie github et hipchat de l’état du déploiement via leurs API respective. Une fois le déploiement terminé je supprime le container.\n\nLes embûches\n\nJe suis tombé sur plusieurs problèmes lors du développement de ce projet. Tout d’abord il faut savoir que quand dans la console on lance docker run en fait en interne docker va faire un create puis un start.\n\nEnsuite il faut que depuis ce container je puisse me connecter en ssh sur les serveurs sur lesquels je vais déployer mes projets, et que je puisse aussi cloner les projets que je vais déployer. Pour ce faire je passe en bind la clé SSH de l’hôte sur lequel est installé deployer vers le root du container comme ceci :\n\n\nimport \"github.com/docker/engine-api/types/container\"\n\nhostConfig := container.HostConfig{\n    Binds: []string{\"/home/deployer/.ssh/id_rsa:/root/.ssh/id_rsa\"},\n}\n\n\n\nJe crée donc mon container avec cette configuration, et je le lance. Comme je ne m’attache pas a celui-ci, tout se passe en background. Donc il faut que j’attende la fin de l’exécution pour récupérer l’exit code.\n\n\nexitCode, err := dockerClient.ContainerWait(ctx, container.ID)\n\n\n\nJe fais beaucoup d’appels à l’API docker dans la même fonction et donc beaucoup d’erreur à gérer. Comme go permet de retourner plusieurs éléments par fonction, à chaque fois que j’ai une erreur, je la remonte. Il me faut donc un moyen pour arrêter le container en cours à la moindre erreur pour ne pas laisser de déchêts sur la route. C’est la qu’intervient le mot-clé defer. Ce mot-clé permet de définir un comportement qui sera exécuté juste avant le retour d’une fonction. Très pratique pour fermer un fichier ou un io.Reader.\n\nOn arrive à la partie la plus intéressante, que j’ai fais en deux fois. La récupération des logs du container une fois l’exécution terminée.\n\n\nimport \"github.com/docker/engine-api/types\"\n\nlogOpts := types.ContainerLogsOptions{\n    ContainerID: container.ID,\n    ShowStdout:  true,\n    ShowStderr:  true,\n    Follow:      true,\n}\n\n\n\nCe qui a été exécuté dans le container a peut–être écrit dans la sortie standard ou la sortie d’erreur. Il faut donc que je récupère les deux. C’est la que je suis tombé sur une partir qui manque de documentation. Quand on demande à l’API docker les logs d’un container avec les deux sorties, docker va les multiplexer pour les mettre dans le même io.Reader. Et pour savoir sur quelle sortie correspond quelle ligne, docker va rajouter un header à chaque ligne pour indiquer si ça correspond a stdin, stdout ou stderr.\n\nIl faut donc je de démultiplexe ce que me renvoit docker pour avoir un résultat lisible. En fouyant un peu sur github et google, je suis tombé sur ce petit package dans le projet docker. et en particulier la fonction StdCopy. Cette fonction permet de démultiplexer une source (io.Reader) et d’écrire stdout et stderr vers deux io.Writer distinct.\n\nEt voilà le tour est joué, j’ai mes logs propre pour chaque déploiement. Sauf que les problèmes ne s’arrêtent pas là. J’ai voulu rajouter de la couleur dans ces logs. Car la commande ansible écrit sur stdout avec différentes couleurs. Pour ce faire il faut que le terminal utilisé supporte la couleur. Il suffit de rajouter ENV TERM xterm dans le Dockerfile du container et ansible va pouvoir afficher de la couleur. Mais ce n’est pas tout, il faut aussi dire à docker d’utiliser un pseudo terminal (PTY) pour le container sinon cette variable d’environnement ne servira a rien. Pour ça il faut modifier la configuration utilisée pour créer le container :\n\n\nimport \"github.com/docker/engine-api/types/container\"\n\nconfig := container.Config{\n    [...]\n    Tty:       true,\n    OpenStdin: true,\n    [...]\n}\n\n\n\nMais voilà, en rajoutant cette configuration, quand on récuère les logs, docker ne va pas multiplexer les sorties stdout et stderr mais va renvoyer la sortie brut du pseudo terminal. Donc il ne faut plus utiliser stdcopy.StdCopy.\n\nComme je l’ai dit plus haut, je voulais avoir une page pour suivre le déploiement et afficher le flux des logs. J’avais deux possibilités pour ça, soit utiliser les Websocket ou Server-sent event. Le premier est en full duplexe, c’est à dire que le serveur et le client peuvent écrire dedans. Le deuxième est dans un sens seulement, le serveur envoi des messages au client. Je suis parti sur Server-sent event, car je n’ai pas besoin que le client (la page web) envoi de message au serveur.\n\nCôté serveur, j’utilise un petit package go qui me permet de créer les événements. Ensuite, pour que dans mon handler du flux je puisse renvoyer les logs sous la forme d’événements j’ai créé une struct qui contient un http.ResponseWriter et la quantité de data envoyée. Avec cette struct j’implémente l’interface io.Writer. Je me suis inspiré de ce que j’ai trouvé dans le projet drone :\n\n\nimport (\n    \"http\"\n    \"strconv\"\n\n    \"github.com/manucorporat/sse\"\n)\n\ntype StreamWriter struct {\n    writer http.ResponseWriter\n    count  int\n}\n\nfunc (w *StreamWriter) Write(data []byte) (int, error) {\n    var err = sse.Encode(w.writer, sse.Event{\n        Id:    strconv.Itoa(w.count),\n        Event: \"message\",\n        Data:  string(data),\n    })\n    w.writer.(http.Flusher).Flush() // ne pas oublier de flush le ResponseWriter à chaque message pour que le serveur envoi l'événement au client.\n    w.count += len(data)\n    return len(data), err\n}\n\n\n\nIl ne me reste plus qu’a copier le io.Reader que me renvoi docker pour les logs dans cet io.Writer. Ne pas oublier de définir le Content-Type à text/event-stream.\n\n\nimport (\n    \"http\"\n    \"io\"\n\n    \"github.com/manucorporat/sse\"\n)\n\nfunc streamHandler(w http.ResponseWriter, req *http.Request) {\n    w.Header().Set(\"Content-Type\", sse.ContentType)\n    reader, _ := dockerClient.ContainerLogs(ctx, logOpts)\n    writer := &StreamWriter{w, 0}\n    io.Copy(writer, reader)\n}\n\n\n\nJe peux donc récupérer ces événements depuis la page du déploiement et les afficher.\n\nRésultat final\n\nJ’ai donc maintenant une application qui reçoit les événements de déploiement de github et qui lance le déploiement dans un container docker. Je peux suivre le tout dans une joli page web. Ainsi est né Deployer.\n\nLa suite du projet ? Avoir une meilleure gestion des releases, savoir qui a déployer quoi à quel moment, un historique des commits déployés, une intégration avec une application github oauth pour simplifier l’ajout de projet à déployer, une sécurisation du webhook github avec un secret, etc. La liste est longue.\n\n    \n      \n        \n          Si vous trouvez une typo, n'hésitez pas à forker et éditer cet article. Merci beaucoup !\n        \n      \n    \n    \n    \n  \n  \n  \n",
   "title" : "Déploiement avec Go et Ansible dans Docker",
   "length" : 8265,
   "uri" : {
      "spec" : "http://blog.luxifer.fr/2016/04/04/deploiement-avec-go-et-ansible-dans-docker/",
      "scheme" : "http",
      "host" : "blog.luxifer.fr",
      "pathBase" : "http://blog.luxifer.fr/2016/04/04/deploiement-avec-go-et-ansible-dans-docker/",
      "prePath" : "http://blog.luxifer.fr"
   },
   "excerpt" : "La semaine dernière je me suis mis en tête de développer une application afin de centraliser tous les déploiements qu’on fait à Xotelia. J’ai fait le tour et il existe en SaaS ou en Open source des solutions pour ça. Je n’en ai pas trouvé qui fonctionne avec ansible (bien que lors d’un déploiement on puisse installer ce qu’on veut), ou alors trop cher. De plus on ne déploie pas chaque branche, seulement master ou une feature-branch qu’on veut tester sur un environnement différent de la production. C’est la que j’ai décidé de développer notre propre outil de déploiement. À savoir que sur chacun de nos projets nous avons un playbook ansible de déploiement, mais que jusqu’à maintenant on les lance sur nos propres machines. Il me fallait aussi un outil qui me permette de lancer une commande dans hipchat pour déclencher un déploiement.",
   "byline" : null,
   "content" : "<div id=\"readability-page-1\" class=\"page\"><div id=\"middle\">\n  <div id=\"article\">\n    <p>La semaine dernière je me suis mis en tête de développer une application afin de centraliser tous les déploiements qu’on fait à <a href=\"https://www.xotelia.com\">Xotelia</a>. J’ai fait le tour et il existe en SaaS ou en Open source des solutions pour ça. Je n’en ai pas trouvé qui fonctionne avec ansible (bien que lors d’un déploiement on puisse installer ce qu’on veut), ou alors trop cher. De plus on ne déploie pas chaque branche, seulement <em>master</em> ou une feature-branch qu’on veut tester sur un environnement différent de la production. C’est la que j’ai décidé de développer notre propre outil de déploiement. À savoir que sur chacun de nos projets nous avons un playbook ansible de déploiement, mais que jusqu’à maintenant on les lance sur nos propres machines. Il me fallait aussi un outil qui me permette de lancer une commande dans hipchat pour déclencher un déploiement.</p>\n\n<p>Étant un grand fan de <a href=\"http://blog.luxifer.fr/2015/03/13/pourquoi-choisir-go/\">go</a> et de docker je me suis dit que ça serait pas mal de pouvoir lancer un container docker, dans lequel je lance ansible, et avoir par dessus un petit frontent avec un flux des logs.</p>\n\n<p>J’ai donc commencé a chercher un client docker en go et je suis tombé sur <a href=\"https://github.com/docker/engine-api\">celui-la</a>. Il me semblait qu’il n’y en avait pas à part dans le core de docker ou alors développé par des tiers, mais je me suis trompé. Et qui plus est, très bien documenté et <em>idiomatic</em>.</p>\n\n<p>Pour le stockage des résultats des builds je me suis tourné vers <a href=\"https://www.rethinkdb.com/\">RethinkDB</a>. Une base de données NoSQL.</p>\n\n<p>Le principe de fonctionnement est simple, quand je reçois un événement de déploiement depuis github, je crée un container à partir d’une image qui contient ansible. Dans ce container je clone le dépôt à déployer et je lance le playbook ansible. Je notifie github et hipchat de l’état du déploiement via leurs API respective. Une fois le déploiement terminé je supprime le container.</p>\n\n<h2 id=\"les-embches\">Les embûches</h2>\n\n<p>Je suis tombé sur plusieurs problèmes lors du développement de ce projet. Tout d’abord il faut savoir que quand dans la console on lance <code class=\"highlighter-rouge\">docker run</code> en fait en interne docker va faire un <code class=\"highlighter-rouge\">create</code> puis un <code class=\"highlighter-rouge\">start</code>.</p>\n\n<p>Ensuite il faut que depuis ce container je puisse me connecter en ssh sur les serveurs sur lesquels je vais déployer mes projets, et que je puisse aussi cloner les projets que je vais déployer. Pour ce faire je passe en <code class=\"highlighter-rouge\">bind</code> la clé SSH de l’hôte sur lequel est installé <code class=\"highlighter-rouge\">deployer</code> vers le root du container comme ceci :</p>\n\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"k\">import</span><span class=\"x\"> </span><span class=\"s\">\"github.com/docker/engine-api/types/container\"</span><span class=\"x\">\n\n</span><span class=\"n\">hostConfig</span><span class=\"x\"> </span><span class=\"o\">:=</span><span class=\"x\"> </span><span class=\"n\">container</span><span class=\"o\">.</span><span class=\"n\">HostConfig</span><span class=\"p\">{</span><span class=\"x\">\n    </span><span class=\"n\">Binds</span><span class=\"o\">:</span><span class=\"x\"> </span><span class=\"p\">[]</span><span class=\"kt\">string</span><span class=\"p\">{</span><span class=\"s\">\"/home/deployer/.ssh/id_rsa:/root/.ssh/id_rsa\"</span><span class=\"p\">},</span><span class=\"x\">\n</span><span class=\"p\">}</span><span class=\"x\">\n</span></code></pre>\n</div>\n\n<p>Je crée donc mon container avec cette configuration, et je le lance. Comme je ne m’attache pas a celui-ci, tout se passe en background. Donc il faut que j’attende la fin de l’exécution pour récupérer l’exit code.</p>\n\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"n\">exitCode</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">err</span><span class=\"x\"> </span><span class=\"o\">:=</span><span class=\"x\"> </span><span class=\"n\">dockerClient</span><span class=\"o\">.</span><span class=\"n\">ContainerWait</span><span class=\"p\">(</span><span class=\"n\">ctx</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">container</span><span class=\"o\">.</span><span class=\"n\">ID</span><span class=\"p\">)</span><span class=\"x\">\n</span></code></pre>\n</div>\n\n<p>Je fais beaucoup d’appels à l’API docker dans la même fonction et donc beaucoup d’erreur à gérer. Comme go permet de retourner plusieurs éléments par fonction, à chaque fois que j’ai une erreur, je la remonte. Il me faut donc un moyen pour arrêter le container en cours à la moindre erreur pour ne pas laisser de déchêts sur la route. C’est la qu’intervient le mot-clé <code class=\"highlighter-rouge\">defer</code>. Ce mot-clé permet de définir un comportement qui sera exécuté juste avant le retour d’une fonction. Très pratique pour fermer un fichier ou un <code class=\"highlighter-rouge\">io.Reader</code>.</p>\n\n<p>On arrive à la partie la plus intéressante, que j’ai fais en deux fois. La récupération des logs du container une fois l’exécution terminée.</p>\n\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"k\">import</span><span class=\"x\"> </span><span class=\"s\">\"github.com/docker/engine-api/types\"</span><span class=\"x\">\n\n</span><span class=\"n\">logOpts</span><span class=\"x\"> </span><span class=\"o\">:=</span><span class=\"x\"> </span><span class=\"n\">types</span><span class=\"o\">.</span><span class=\"n\">ContainerLogsOptions</span><span class=\"p\">{</span><span class=\"x\">\n    </span><span class=\"n\">ContainerID</span><span class=\"o\">:</span><span class=\"x\"> </span><span class=\"n\">container</span><span class=\"o\">.</span><span class=\"n\">ID</span><span class=\"p\">,</span><span class=\"x\">\n    </span><span class=\"n\">ShowStdout</span><span class=\"o\">:</span><span class=\"x\">  </span><span class=\"no\">true</span><span class=\"p\">,</span><span class=\"x\">\n    </span><span class=\"n\">ShowStderr</span><span class=\"o\">:</span><span class=\"x\">  </span><span class=\"no\">true</span><span class=\"p\">,</span><span class=\"x\">\n    </span><span class=\"n\">Follow</span><span class=\"o\">:</span><span class=\"x\">      </span><span class=\"no\">true</span><span class=\"p\">,</span><span class=\"x\">\n</span><span class=\"p\">}</span><span class=\"x\">\n</span></code></pre>\n</div>\n\n<p>Ce qui a été exécuté dans le container a peut–être écrit dans la sortie standard ou la sortie d’erreur. Il faut donc que je récupère les deux. C’est la que je suis tombé sur une partir qui manque de documentation. Quand on demande à l’API docker les logs d’un container avec les deux sorties, docker va les multiplexer pour les mettre dans le même <code class=\"highlighter-rouge\">io.Reader</code>. Et pour savoir sur quelle sortie correspond quelle ligne, docker va rajouter un header à chaque ligne pour indiquer si ça correspond a <code class=\"highlighter-rouge\">stdin</code>, <code class=\"highlighter-rouge\">stdout</code> ou <code class=\"highlighter-rouge\">stderr</code>.</p>\n\n<p>Il faut donc je de démultiplexe ce que me renvoit docker pour avoir un résultat lisible. En fouyant un peu sur github et google, je suis tombé sur ce <a href=\"https://godoc.org/github.com/docker/docker/pkg/stdcopy\">petit package</a> dans le projet docker. et en particulier la fonction <a href=\"https://godoc.org/github.com/docker/docker/pkg/stdcopy#StdCopy\"><code class=\"highlighter-rouge\">StdCopy</code></a>. Cette fonction permet de démultiplexer une source (<code class=\"highlighter-rouge\">io.Reader</code>) et d’écrire <code class=\"highlighter-rouge\">stdout</code> et <code class=\"highlighter-rouge\">stderr</code> vers deux <code class=\"highlighter-rouge\">io.Writer</code> distinct.</p>\n\n<p>Et voilà le tour est joué, j’ai mes logs propre pour chaque déploiement. Sauf que les problèmes ne s’arrêtent pas là. J’ai voulu rajouter de la couleur dans ces logs. Car la commande ansible écrit sur <code class=\"highlighter-rouge\">stdout</code> avec différentes couleurs. Pour ce faire il faut que le terminal utilisé supporte la couleur. Il suffit de rajouter <code class=\"highlighter-rouge\">ENV TERM xterm</code> dans le <code class=\"highlighter-rouge\">Dockerfile</code> du container et ansible va pouvoir afficher de la couleur. Mais ce n’est pas tout, il faut aussi dire à docker d’utiliser un pseudo terminal (PTY) pour le container sinon cette variable d’environnement ne servira a rien. Pour ça il faut modifier la configuration utilisée pour créer le container :</p>\n\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"k\">import</span><span class=\"x\"> </span><span class=\"s\">\"github.com/docker/engine-api/types/container\"</span><span class=\"x\">\n\n</span><span class=\"n\">config</span><span class=\"x\"> </span><span class=\"o\">:=</span><span class=\"x\"> </span><span class=\"n\">container</span><span class=\"o\">.</span><span class=\"n\">Config</span><span class=\"p\">{</span><span class=\"x\">\n    </span><span class=\"p\">[</span><span class=\"o\">...</span><span class=\"p\">]</span><span class=\"x\">\n    </span><span class=\"n\">Tty</span><span class=\"o\">:</span><span class=\"x\">       </span><span class=\"no\">true</span><span class=\"p\">,</span><span class=\"x\">\n    </span><span class=\"n\">OpenStdin</span><span class=\"o\">:</span><span class=\"x\"> </span><span class=\"no\">true</span><span class=\"p\">,</span><span class=\"x\">\n    </span><span class=\"p\">[</span><span class=\"o\">...</span><span class=\"p\">]</span><span class=\"x\">\n</span><span class=\"p\">}</span><span class=\"x\">\n</span></code></pre>\n</div>\n\n<p>Mais voilà, en rajoutant cette configuration, quand on récuère les logs, docker ne va pas multiplexer les sorties <code class=\"highlighter-rouge\">stdout</code> et <code class=\"highlighter-rouge\">stderr</code> mais va renvoyer la sortie brut du pseudo terminal. Donc il ne faut plus utiliser <code class=\"highlighter-rouge\">stdcopy.StdCopy</code>.</p>\n\n<p>Comme je l’ai dit plus haut, je voulais avoir une page pour suivre le déploiement et afficher le flux des logs. J’avais deux possibilités pour ça, soit utiliser les <a href=\"https://developer.mozilla.org/fr/docs/WebSockets\">Websocket</a> ou <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events\">Server-sent event</a>. Le premier est en full duplexe, c’est à dire que le serveur et le client peuvent écrire dedans. Le deuxième est dans un sens seulement, le serveur envoi des messages au client. Je suis parti sur Server-sent event, car je n’ai pas besoin que le client (la page web) envoi de message au serveur.</p>\n\n<p>Côté serveur, j’utilise un petit <a href=\"https://godoc.org/github.com/manucorporat/sse\">package</a> go qui me permet de créer les événements. Ensuite, pour que dans mon handler du flux je puisse renvoyer les logs sous la forme d’événements j’ai créé une <code class=\"highlighter-rouge\">struct</code> qui contient un <code class=\"highlighter-rouge\">http.ResponseWriter</code> et la quantité de data envoyée. Avec cette <code class=\"highlighter-rouge\">struct</code> j’implémente l’interface <code class=\"highlighter-rouge\">io.Writer</code>. Je me suis inspiré de ce que j’ai trouvé dans le projet <a href=\"https://github.com/drone/drone\">drone</a> :</p>\n\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"k\">import</span><span class=\"x\"> </span><span class=\"p\">(</span><span class=\"x\">\n    </span><span class=\"s\">\"http\"</span><span class=\"x\">\n    </span><span class=\"s\">\"strconv\"</span><span class=\"x\">\n\n    </span><span class=\"s\">\"github.com/manucorporat/sse\"</span><span class=\"x\">\n</span><span class=\"p\">)</span><span class=\"x\">\n\n</span><span class=\"k\">type</span><span class=\"x\"> </span><span class=\"n\">StreamWriter</span><span class=\"x\"> </span><span class=\"k\">struct</span><span class=\"x\"> </span><span class=\"p\">{</span><span class=\"x\">\n    </span><span class=\"n\">writer</span><span class=\"x\"> </span><span class=\"n\">http</span><span class=\"o\">.</span><span class=\"n\">ResponseWriter</span><span class=\"x\">\n    </span><span class=\"n\">count</span><span class=\"x\">  </span><span class=\"kt\">int</span><span class=\"x\">\n</span><span class=\"p\">}</span><span class=\"x\">\n\n</span><span class=\"k\">func</span><span class=\"x\"> </span><span class=\"p\">(</span><span class=\"n\">w</span><span class=\"x\"> </span><span class=\"o\">*</span><span class=\"n\">StreamWriter</span><span class=\"p\">)</span><span class=\"x\"> </span><span class=\"n\">Write</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"x\"> </span><span class=\"p\">[]</span><span class=\"kt\">byte</span><span class=\"p\">)</span><span class=\"x\"> </span><span class=\"p\">(</span><span class=\"kt\">int</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"kt\">error</span><span class=\"p\">)</span><span class=\"x\"> </span><span class=\"p\">{</span><span class=\"x\">\n    </span><span class=\"k\">var</span><span class=\"x\"> </span><span class=\"n\">err</span><span class=\"x\"> </span><span class=\"o\">=</span><span class=\"x\"> </span><span class=\"n\">sse</span><span class=\"o\">.</span><span class=\"n\">Encode</span><span class=\"p\">(</span><span class=\"n\">w</span><span class=\"o\">.</span><span class=\"n\">writer</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">sse</span><span class=\"o\">.</span><span class=\"n\">Event</span><span class=\"p\">{</span><span class=\"x\">\n        </span><span class=\"n\">Id</span><span class=\"o\">:</span><span class=\"x\">    </span><span class=\"n\">strconv</span><span class=\"o\">.</span><span class=\"n\">Itoa</span><span class=\"p\">(</span><span class=\"n\">w</span><span class=\"o\">.</span><span class=\"n\">count</span><span class=\"p\">),</span><span class=\"x\">\n        </span><span class=\"n\">Event</span><span class=\"o\">:</span><span class=\"x\"> </span><span class=\"s\">\"message\"</span><span class=\"p\">,</span><span class=\"x\">\n        </span><span class=\"n\">Data</span><span class=\"o\">:</span><span class=\"x\">  </span><span class=\"kt\">string</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">),</span><span class=\"x\">\n    </span><span class=\"p\">})</span><span class=\"x\">\n    </span><span class=\"n\">w</span><span class=\"o\">.</span><span class=\"n\">writer</span><span class=\"o\">.</span><span class=\"p\">(</span><span class=\"n\">http</span><span class=\"o\">.</span><span class=\"n\">Flusher</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">Flush</span><span class=\"p\">()</span><span class=\"x\"> </span><span class=\"c\">// ne pas oublier de flush le ResponseWriter à chaque message pour que le serveur envoi l'événement au client.</span><span class=\"x\">\n    </span><span class=\"n\">w</span><span class=\"o\">.</span><span class=\"n\">count</span><span class=\"x\"> </span><span class=\"o\">+=</span><span class=\"x\"> </span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">)</span><span class=\"x\">\n    </span><span class=\"k\">return</span><span class=\"x\"> </span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">),</span><span class=\"x\"> </span><span class=\"n\">err</span><span class=\"x\">\n</span><span class=\"p\">}</span><span class=\"x\">\n</span></code></pre>\n</div>\n\n<p>Il ne me reste plus qu’a copier le <code class=\"highlighter-rouge\">io.Reader</code> que me renvoi docker pour les logs dans cet <code class=\"highlighter-rouge\">io.Writer</code>. Ne pas oublier de définir le <code class=\"highlighter-rouge\">Content-Type</code> à <code class=\"highlighter-rouge\">text/event-stream</code>.</p>\n\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"k\">import</span><span class=\"x\"> </span><span class=\"p\">(</span><span class=\"x\">\n    </span><span class=\"s\">\"http\"</span><span class=\"x\">\n    </span><span class=\"s\">\"io\"</span><span class=\"x\">\n\n    </span><span class=\"s\">\"github.com/manucorporat/sse\"</span><span class=\"x\">\n</span><span class=\"p\">)</span><span class=\"x\">\n\n</span><span class=\"k\">func</span><span class=\"x\"> </span><span class=\"n\">streamHandler</span><span class=\"p\">(</span><span class=\"n\">w</span><span class=\"x\"> </span><span class=\"n\">http</span><span class=\"o\">.</span><span class=\"n\">ResponseWriter</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">req</span><span class=\"x\"> </span><span class=\"o\">*</span><span class=\"n\">http</span><span class=\"o\">.</span><span class=\"n\">Request</span><span class=\"p\">)</span><span class=\"x\"> </span><span class=\"p\">{</span><span class=\"x\">\n    </span><span class=\"n\">w</span><span class=\"o\">.</span><span class=\"n\">Header</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">Set</span><span class=\"p\">(</span><span class=\"s\">\"Content-Type\"</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">sse</span><span class=\"o\">.</span><span class=\"n\">ContentType</span><span class=\"p\">)</span><span class=\"x\">\n    </span><span class=\"n\">reader</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">_</span><span class=\"x\"> </span><span class=\"o\">:=</span><span class=\"x\"> </span><span class=\"n\">dockerClient</span><span class=\"o\">.</span><span class=\"n\">ContainerLogs</span><span class=\"p\">(</span><span class=\"n\">ctx</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">logOpts</span><span class=\"p\">)</span><span class=\"x\">\n    </span><span class=\"n\">writer</span><span class=\"x\"> </span><span class=\"o\">:=</span><span class=\"x\"> </span><span class=\"o\">&amp;</span><span class=\"n\">StreamWriter</span><span class=\"p\">{</span><span class=\"n\">w</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"m\">0</span><span class=\"p\">}</span><span class=\"x\">\n    </span><span class=\"n\">io</span><span class=\"o\">.</span><span class=\"n\">Copy</span><span class=\"p\">(</span><span class=\"n\">writer</span><span class=\"p\">,</span><span class=\"x\"> </span><span class=\"n\">reader</span><span class=\"p\">)</span><span class=\"x\">\n</span><span class=\"p\">}</span><span class=\"x\">\n</span></code></pre>\n</div>\n\n<p>Je peux donc récupérer ces événements depuis la page du déploiement et les afficher.</p>\n\n<h2 id=\"rsultat-final\">Résultat final</h2>\n\n<p>J’ai donc maintenant une application qui reçoit les événements de déploiement de github et qui lance le déploiement dans un container docker. Je peux suivre le tout dans une joli page web. Ainsi est né <a href=\"https://github.com/Xotelia/deployer\">Deployer</a>.</p>\n\n<p>La suite du projet ? Avoir une meilleure gestion des releases, savoir qui a déployer quoi à quel moment, un historique des commits déployés, une intégration avec une application github oauth pour simplifier l’ajout de projet à déployer, une sécurisation du webhook github avec un secret, etc. La liste est longue.</p>\n\n    <p>\n      <small>\n        <em>\n          Si vous trouvez une typo, n'hésitez pas à <a href=\"https://github.com/luxifer/luxifer.github.io/edit/master/_posts/2016-04-04-deploiement-avec-go-et-ansible-dans-docker.md\">forker et éditer cet article</a>. Merci beaucoup !\n        </em>\n      </small>\n    </p>\n    \n    \n  </div>\n  \n  \n</div></div>",
   "dir" : null
}
Docker Pull Command
Owner
luxifer
Source Repository

Comments (0)