<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jeckel-lab.fr/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jeckel-lab.fr/" rel="alternate" type="text/html" hreflang="fr" /><updated>2026-04-11T20:42:02+00:00</updated><id>https://jeckel-lab.fr/feed.xml</id><title type="html">Jeckel-Lab</title><subtitle>Freelance : Expert PHP / Lead Developer / Software Architect J&apos;accompagne les équipes IT dans l&apos;industrialisation de leurs projets Web, à la fois sur l&apos;architecture, la qualité et l&apos;intégration continue, la gestion de projet et l&apos;agilité.</subtitle><author><name>Julien Mercier-Rojas</name></author><entry><title type="html">Comment debugger un Job dans CircleCI</title><link href="https://jeckel-lab.fr/ci-cd/2024/02/15/debugger-un-job-circleci.html" rel="alternate" type="text/html" title="Comment debugger un Job dans CircleCI" /><published>2024-02-15T00:00:00+00:00</published><updated>2024-02-15T00:00:00+00:00</updated><id>https://jeckel-lab.fr/ci-cd/2024/02/15/debugger-un-job-circleci</id><content type="html" xml:base="https://jeckel-lab.fr/ci-cd/2024/02/15/debugger-un-job-circleci.html"><![CDATA[<p><a href="https://circleci.com/">CircleCI</a> est un outil très complet pour créer des workflows d’intégration continue et de déploiement continu. C’est un outil très polyvalent qui a, en plus, l’avantage de ne pas être dépendant de votre répertoire de code. En effet, on peut l’utiliser aussi bien avec <a href="https://github.com/">Github</a>, <a href="https://bitbucket.org/">Bitbucket</a> ou encore <a href="https://about.gitlab.com/">GitLab</a>.</p>

<p>En revanche, même si tout fonctionne très bien localement, lorsque l’on déploie les opérations d’intégration continue sur CircleCI, il arrive très souvent (surtout au début) que l’on rencontre des difficultés de configuration ou des problèmes de permissions spécifiques à l’environnement d’exécution.</p>

<p>Je vais donc vous présenter ici deux solutions qui peuvent vous aider à vous en sortir rapidement.</p>

<h2 id="exécuter-circleci-localement">Exécuter CircleCI localement</h2>

<p>CircleCI propose un client permettant d’exécuter ses jobs localement. Cet outil permet de lancer manuellement et localement les jobs presque comme s’ils s’exécutaient directement dans l’environnement distant.</p>

<p>Je dis bien presque, parce qu’il va y avoir quelques différences liées au réseau et à la configuration de votre machine, mais dans la majorité des cas, ces différences seront transparentes.</p>

<h3 id="installation-et-configuration">Installation et configuration</h3>

<p>La première étape consiste, évidemment, à installer ce client. Je vous invite donc à consulter la <a href="https://circleci.com/docs/local-cli/">documentation d’installation propre à votre système</a>.</p>

<p>Ensuite, vous allez devoir le configurer pour lui permettre d’accéder à votre environnement CircleCI, à vos paramètres et à vos permissions.</p>

<p>Il vous faut tout d’abord générer un <strong>Personal API Token</strong> <a href="https://app.circleci.com/settings/user/tokens">directement sur le site</a>.</p>

<p>Une fois ce token récupéré, configurez CircleCI-Cli :</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> circleci setup
</code></pre></div></div>
<p>Et donc, lui donner le token quand il vous le demande.</p>

<h3 id="exécution-dun-job-sur-la-machine-locale">Exécution d’un job sur la machine locale</h3>

<p>Une fois la configuration terminée, vous pourrez lancer les jobs que vous souhaitez en vous rendant dans votre projet :</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> circleci <span class="nb">local </span>execute &lt;JOB_NAME&gt;
</code></pre></div></div>
<p>Avec <JOB_NAME> le nom d'un job défini dans votre fichier `.circleci/config.yml`</JOB_NAME></p>

<p>Dans la majorité des cas, cela vous permettra de tester votre configuration CircleCI sans avoir à pousser votre configuration à chaque changement et attendre de voir ce qui se passe sur l’interface web.</p>

<h2 id="debugger-directement-sur-circleci-via-une-connection-ssh">Debugger directement sur CircleCI via une connection ssh</h2>

<p><strong>CircleCI</strong> propose une autre solution pour déboguer les jobs récalcitrants. En effet, il est possible de relancer un job en échec en ouvrant une connexion SSH qui vous permet de vous connecter directement à l’instance où s’exécute le job et de le déboguer en temps réel.</p>

<p>Pour celà, rendez-vous sur la page du job en erreur, puis en haut à droite, déplier le menu sous “Rerun”, et vous verrez l’option “Rerun job with SSH”</p>

<p><img src="/images/CircleCi-RerunJobWithSsh.png" alt="CircleCi - ReRun Job with Ssh" class="center-image" /></p>

<p>Cette action va relancer le job en question en ouvrant une connexion SSH sur l’environnement d’exécution du job.</p>

<ul>
  <li>Cette connection n’est disponible que pendant 1h.</li>
  <li>Pour se connecter, vous aurez besoin d’une clé SSH, en général, il s’agit de celle utilisée par Git sur votre répertoire (CircleCI aura récupéré la clé publique).</li>
</ul>

<p>Dans le log de nouveau Job vous verrez un bloc supplémentaire “Enable SSH” à l’intérieur duquel vous trouverez les informations nécessaires à vous connecter.</p>

<p><img src="/images/CircleCi-ConnectToJobWithSsh.png" alt="CircleCi - Connection au job via Ssh" class="center-image" /></p>

<p>Il vous suffit alors de vous connecter depuis votre terminal via SSH en précisant votre clé (si ce n’est pas votre clé par défaut), par exemple :</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> ssh <span class="nt">-p</span> 64535 <span class="nt">-i</span> .ssh/keys/id_ed25519 3.91.14.100
</code></pre></div></div>

<p>Vous serez ainsi connecté directement à la machine sur laquelle le job est en cours d’exécution. Vous pourrez donc y effectuer tous les tests en temps réel pour identifier ce qui bloque votre build : fichier manquant, problème de permission, connexion réseau, etc.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Voici deux méthodes différentes répondant toutes deux à des cas d’usage différents qui pourront vous aider à déboguer et à faire évoluer vos pipelines d’intégration continue et de déploiement continu avec CircleCI.</p>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="ci-cd" /><category term="ci-cd" /><category term="page-ci" /><summary type="html"><![CDATA[Explorez les meilleures pratiques pour l'intégration et le déploiement continus avec CircleCI. Découvrez comment configurer efficacement vos workflows, déboguer en local et via SSH, et optimiser vos pipelines CI/CD pour une automatisation sans faille. Que vous soyez débutant ou avancé, ce guide vous offre des conseils précieux, des tutoriels CircleCI, et des astuces pour améliorer la sécurité, la performance et l'efficacité de vos projets. Maîtrisez CircleCI avec des solutions innovantes pour GitHub, Bitbucket et GitLab.]]></summary></entry><entry><title type="html">Petite histoire de l’immutabilité en PHP</title><link href="https://jeckel-lab.fr/php/2023/10/02/histoire-immutabilite-en-php.html" rel="alternate" type="text/html" title="Petite histoire de l’immutabilité en PHP" /><published>2023-10-02T00:00:00+00:00</published><updated>2023-10-02T00:00:00+00:00</updated><id>https://jeckel-lab.fr/php/2023/10/02/histoire-immutabilite-en-php</id><content type="html" xml:base="https://jeckel-lab.fr/php/2023/10/02/histoire-immutabilite-en-php.html"><![CDATA[<p>L’immutabilité est un concept qui a gagné en popularité via la croissance de la programmation fonctionnelle, cependant PHP étant un langage initialement principalement utilisé pour le développement d’application Web, l’immutabilité n’a été intégré que tardivement et progressivement.</p>

<h2 id="comprendre-limmutabilité">Comprendre l’immutabilité</h2>

<p>L’immutabilité en programmation signifie que quelque chose ne peut être modifié une fois qu’il a été créé, il ne peut pas <strong>muter</strong>.</p>

<p>Dans le code, cela se traduit par l’utilisation de variables ou d’objets qui ne peuvent pas être modifiés une fois qu’ils ont été initialisés ou instanciés. Si l’on doit faire une modification, on créera plutôt une autre objet à partir du premier avec les nouvelles propriétés.</p>

<h3 id="pourquoi-utiliser-limmutabilité">Pourquoi utiliser l’immutabilité ?</h3>

<ol>
  <li><strong>Sécurité</strong> : Cela garantit que l’objet ne peut pas être modifié par une autre fonction ou un autre service par erreur</li>
  <li><strong>Facilité de débugging</strong> : Il n’est pas nécessaire de suivre les modifications de cet objet</li>
  <li><strong>Prévisibilité</strong> : Cela rend le comportement du programme plus prévisible</li>
  <li><strong>Performance</strong> : Si le langage le permet, savoir qu’un objet est immutable va permettre au compilateur/interpréteur d’en optimiser le fonctionnement.</li>
</ol>

<h3 id="quand-utiliser-des-objets-immutables">Quand utiliser des objets immutables ?</h3>

<p>On utilisera des objets immutable à chaque fois que l’on a besoin de garantir que les données ne sont pas modifiables, que la nature de cet objet et son utilisation en font un objet dont la modification a posteriori serait le signe d’un bug ou d’une erreur de conception.</p>

<p>Par exemple :</p>
<ul>
  <li><strong>Événement</strong> : Un objet décrivant un événement décris quelque chose qui est dans le passé, et comme on ne réécrit pas l’histoire, le passé est immutable, ce type d’objet devrait être immutable</li>
  <li><strong>Commande</strong> : Il s’agit d’un objet décrivant une instruction. Une fois l’instruction émise, elle devient immuable</li>
  <li><strong>DTO</strong> : ou Data Transfert Object, l’objectif d’un DTO est d’encapsuler des données complexes lors de leur transfert d’une fonction à une autre, d’un contexte à un autre. Ce n’est pas obligatoire, mais il peut être intéressant de faire de ces DTO des objets immutables afin de garantir qu’aucune modification ne peut avoir lieu dans les échanges suivants.</li>
</ul>

<h2 id="php-et-immutabilité">PHP et immutabilité</h2>

<p>Bien que l’immutabilité ne soit pas un élément clé de PHP, le langage a évolué pour donner, version après version les éléments nécessaires à une implémentation fiable.</p>

<h3 id="php-4-et-antérieur">PHP 4 et antérieur</h3>

<p>Dans les premières versions de PHP, l’immutabilité n’était pas vraiment un sujet de discussion. Le langage était principalement procédural et orienté vers la facilité d’utilisation plutôt que vers des concepts avancés comme l’immutabilité.</p>

<h3 id="juillet-2004-php-50-et-la-poo">Juillet 2004, PHP 5.0 et la POO</h3>

<p>La version 5.0 sortie en juillet 2004 introduit la programmation orienté objet au langage, pas encore de notion d’immutabilité, mais cette version inclue déjà le mot clé <code class="language-plaintext highlighter-rouge">final</code> qui peut être utilisé pour une classe, rendant impossible la surcharge de celle-ci.</p>

<p>Il est alors déjà possible de créer un objet <code class="language-plaintext highlighter-rouge">final</code> avec toutes les propriétés privées.</p>

<p>Il est donc déjà possible pour les développeurs de concevoir des objets immutables, la seule garantie étant la qualité du code.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/**
 * Immutable Event object in php 5.0
 */</span>
<span class="k">final</span> <span class="kd">class</span> <span class="nc">Event</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="nv">$name</span><span class="p">;</span>
    <span class="k">private</span> <span class="nv">$date</span><span class="p">;</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$date</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">name</span> <span class="o">=</span> <span class="nv">$name</span><span class="p">;</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">date</span> <span class="o">=</span> <span class="nv">$date</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">getName</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">getDate</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">date</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">changeName</span><span class="p">(</span><span class="nv">$newName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// On ne modifie pas l'objet, on retourne un nouvel objet avec </span>
        <span class="c1">// les nouvelles valeurs</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nc">Event</span><span class="p">(</span><span class="nv">$newName</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">date</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="juin-2013-php-55-et-lobjet-datetimeimmutable">Juin 2013, PHP 5.5 et l’objet DateTimeImmutable</h3>

<p>En juin 2013, PHP sort en version 5.5 et avec cette version apparait la première notion d’immutabilité sous la forme d’un objet interne du langage : <code class="language-plaintext highlighter-rouge">DateTimeImmutable</code>.</p>

<p>Cet objet dispose de la même signature que l’objet <code class="language-plaintext highlighter-rouge">DateTime</code> dont il partage l’interface <code class="language-plaintext highlighter-rouge">DateTimeInterface</code>.</p>

<p><img src="/images/DateTimeInterface.png" alt="Diagramme de classe montrant les objets DateTime et DateTimeImmutable qui implémentent DateTimeInterface" class="center-image" /></p>

<p>Avec cette nouvelle implémentation, l’objet <code class="language-plaintext highlighter-rouge">DateTimeImmutable</code> peut remplacer l’objet <code class="language-plaintext highlighter-rouge">DateTime</code> historique par son équivalent immutable.</p>

<p>Cette première implémentation résout de nombreux problèmes courants, tels que le calcul d’une date de fin en fonction d’une date de début par exemple.</p>

<p>Avec l’objet <code class="language-plaintext highlighter-rouge">DateTime</code> :</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$start</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="p">(</span><span class="s2">"now"</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$start</span><span class="o">-&gt;</span><span class="nf">format</span><span class="p">(</span><span class="s1">'d/m/Y'</span><span class="p">);</span> <span class="c1">// ==&gt; 01/10/2023</span>

<span class="nv">$end</span> <span class="o">=</span> <span class="nv">$start</span><span class="o">-&gt;</span><span class="nf">modify</span><span class="p">(</span><span class="s1">'+1 day);

echo $end-&gt;format('</span><span class="n">d</span><span class="o">/</span><span class="n">m</span><span class="o">/</span><span class="nc">Y</span><span class="p">);</span>    <span class="c1">// ==&gt; 02/10/2023</span>
<span class="k">echo</span> <span class="nv">$start</span><span class="o">-&gt;</span><span class="nf">format</span><span class="p">(</span><span class="err">'</span><span class="n">d</span><span class="o">/</span><span class="n">m</span><span class="o">/</span><span class="nc">Y</span><span class="p">);</span>  <span class="c1">// ==&gt; 02/10/2023 !!</span>
</code></pre></div></div>
<p>L’appel à la fonction <code class="language-plaintext highlighter-rouge">modify</code> a modifié l’objet date en même temps qu’il en a retourné l’instance, <code class="language-plaintext highlighter-rouge">$end</code> et <code class="language-plaintext highlighter-rouge">$start</code> sont deux références vers le même objet.</p>

<p>Avec l’objet <code class="language-plaintext highlighter-rouge">DateTimeImmutable</code> :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$start</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTimeImmutable</span><span class="p">(</span><span class="s2">"now"</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$start</span><span class="o">-&gt;</span><span class="nf">format</span><span class="p">(</span><span class="s1">'d/m/Y'</span><span class="p">);</span> <span class="c1">// ==&gt; 01/10/2023</span>

<span class="nv">$end</span> <span class="o">=</span> <span class="nv">$start</span><span class="o">-&gt;</span><span class="nf">modify</span><span class="p">(</span><span class="s1">'+1 day);

echo $end-&gt;format('</span><span class="n">d</span><span class="o">/</span><span class="n">m</span><span class="o">/</span><span class="nc">Y</span><span class="p">);</span>    <span class="c1">// ==&gt; 02/10/2023</span>
<span class="k">echo</span> <span class="nv">$start</span><span class="o">-&gt;</span><span class="nf">format</span><span class="p">(</span><span class="err">'</span><span class="n">d</span><span class="o">/</span><span class="n">m</span><span class="o">/</span><span class="nc">Y</span><span class="p">);</span>  <span class="c1">// ==&gt; 01/10/2023</span>
</code></pre></div></div>
<p>L’appel à la méthode <code class="language-plaintext highlighter-rouge">modify</code> a fait un clone de l’objet <code class="language-plaintext highlighter-rouge">$start</code> avant de le modifier. Ainsi, <code class="language-plaintext highlighter-rouge">$start</code> et <code class="language-plaintext highlighter-rouge">$end</code> sont bien deux variables différentes.</p>

<h3 id="décembre-2015-php-7-lobjet-devient-sérieux">Décembre 2015, php 7, l’objet devient sérieux</h3>

<p>L’arrivée de php 7.0 en décembre 2015 et les versions suivantes ont apporté beaucoup d’amélioration au support de la programmation objet.</p>

<p>On notera en particulier :</p>
<ul>
  <li>la déclaration du typage scalaire des paramètres de méthode</li>
  <li>la déclaration du type de retour des méthodes</li>
  <li>les types nullables</li>
  <li>les classes anonymes</li>
  <li>et de nombreuses améliorations de performance</li>
</ul>

<p>Le support de la programmation objet devient une préoccupation essentielle du langage, ces évolutions apportent de nouveaux outils à la création d’objet immutable, mais concrètement, toujours aucune réelle implémentation concrète.</p>

<h3 id="novembre-2020-php-80--rien-de-concrêt">Novembre 2020, php 8.0 : rien de concrêt</h3>

<p>PHP 8.0 arrive en novembre 2020, il apporte de grands changements dans le langage, dans son cœur principalement, mais dans le langage aussi.</p>

<p>Bien que cette version n’apporte rien de concret, elle définit les bases nécessaires.</p>

<h3 id="novembre-2021-php-81--propriété-readonly">Novembre 2021, php 8.1 : propriété readonly</h3>

<p>Personnellement, je considère cette version comme la véritable version 8 de php, car elle apporte beaucoup plus de choses utiles au langage.</p>

<p>Tout d’abord, on trouve la possibilité de déclarer des propriétés comme <code class="language-plaintext highlighter-rouge">readonly</code> dans une classe. Une propriété <code class="language-plaintext highlighter-rouge">readonly</code> ne peut être définie qu’une seule fois lors de la création de l’objet, elle devient ensuite non modifiable.</p>

<p>Si on reprend notre objet <code class="language-plaintext highlighter-rouge">Event</code> de la version <code class="language-plaintext highlighter-rouge">5.0</code> de PHP, on obtient une version plus simple, plus facile à utiliser et plus sécurisée (le côté immutable étant inscrit dans le code)</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">Event</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
        <span class="k">public</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="nv">$name</span><span class="p">,</span> 
        <span class="k">public</span> <span class="k">readonly</span> <span class="err">\</span><span class="nc">DateTimeImmutable</span> <span class="nv">$date</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">changeName</span><span class="p">(</span><span class="nv">$newName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// On ne modifie pas l'objet, on retourne un nouvel objet avec </span>
        <span class="c1">// les nouvelles valeurs</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nc">Event</span><span class="p">(</span><span class="nv">$newName</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">date</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Attention, dans cette version de php, la déclaration dynamique de propriété est toujours possible, la classe n’est pas encore totalement immutable, mais on s’en approche.</p>

<h3 id="décembre-2022-php-82--la-classe-readonly">Décembre 2022, php 8.2 : la classe readonly</h3>

<p>La voilà, enfin, la classe immutable. La version 8.2 (la dernière lorsque j’écris ce post, la 8.3 doit arriver dans quelques mois) apporte enfin un support digne de ce nom de l’immutabilité.</p>

<p>En effet, cette version de PHP permet de définir directement au niveau de la déclaration de la classe que celle-ci sera entièrement <code class="language-plaintext highlighter-rouge">readonly</code>, c’est-à-dire immutable. De plus, sur cette classe, la création dynamique de propriété est interdite.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="k">readonly</span> <span class="kd">class</span> <span class="nc">Event</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="nv">$name</span><span class="p">,</span> 
        <span class="k">public</span> <span class="err">\</span><span class="nc">DateTimeImmutable</span> <span class="nv">$date</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">changeName</span><span class="p">(</span><span class="nv">$newName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// On ne modifie pas l'objet, on retourne un nouvel objet avec </span>
        <span class="c1">// les nouvelles valeurs</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nc">Event</span><span class="p">(</span><span class="nv">$newName</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">date</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="phpstan-et-psalm">PHPStan et Psalm</h2>

<p><a href="https://phpstan.org/">PHPStan</a> et <a href="https://psalm.dev/">Psalm</a> sont deux outils d’analyse statique de code qui permettent de rajouter une passe de contrôle et qualité sur le code d’une application.</p>

<p>Ces outils vont analyser le code de l’application et vérifier comment les différentes variables et objets sont construits et utilisés afin de détecter toute sorte de violation.</p>

<p>Il est alors possible de préciser à l’outil qu’une classe doit être immutable, l’analyseur va vérifier dans l’application que cette règle est bien respectée, c’est dire que l’objet, une fois instancié n’est jamais modifié, ou que toute modification passe par l’instanciation d’un nouvel objet.</p>

<p>L’avantage de ces outils est qu’ils vont tous deux permettre de rajouter un support de l’immutabilité aux versions de PHP qui en sont dépourvue.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/**
 * @immutable
 */</span>
<span class="k">final</span> <span class="kd">class</span> <span class="nc">Event</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="nv">$name</span><span class="p">,</span> 
        <span class="k">public</span> <span class="err">\</span><span class="nc">DateTimeImmutable</span> <span class="nv">$date</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">changeName</span><span class="p">(</span><span class="nv">$newName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// On ne modifie pas l'objet, on retourne un nouvel objet avec </span>
        <span class="c1">// les nouvelles valeurs</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nc">Event</span><span class="p">(</span><span class="nv">$newName</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">date</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ici, même en version 7.4 de php, l’immutabilité bien que n’étant pas prévue par le langage sera garantie par les outils d’analyse pour lesquels on a défini dans le Docblock que cette classe doit être immutable.</p>

<h2 id="dans-la-pratique">Dans la pratique</h2>

<p>L’immutabilité, jusqu’à la dernière version de PHP, relève davantage de la discipline et de bonnes pratiques qu’une caractéristique intégrée au langage. Cependant, avec l’évolution du langage et des écosystèmes qui l’entourent, il est probable que l’immutabilité devienne de plus en plus courante dans le développement PHP.</p>

<p>Au final, quelques conseils pour faire des objets immutables en PHP</p>
<ol>
  <li>Passer en PHP 8.2 (Ok, pas toujours possible)</li>
  <li>Utiliser un outil d’analyse statique comme <a href="https://phpstan.org/">PHPStan</a> ou <a href="https://psalm.dev/">Psalm</a>, et surtout, ajouter les dans votre pipeline d’intégration continue (sinon, ça ne sert à rien)</li>
  <li>Utiliser les <code class="language-plaintext highlighter-rouge">readonly</code> (php 8.1 et 8.2)</li>
  <li>Utiliser le <code class="language-plaintext highlighter-rouge">final</code></li>
  <li>Penser au “copy on write”, c’est-à-dire de retourner une nouvelle instance de l’objet à chaque modification.</li>
</ol>

<p>Et voilà, vous êtes prêt.</p>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="php" /><category term="php" /><category term="page-expert-php" /><summary type="html"><![CDATA[Découvrez l'évolution de l'immutabilité en PHP, de ses débuts jusqu'aux dernières versions. Apprenez pourquoi et comment utiliser l'immutabilité dans vos projets PHP.]]></summary></entry><entry><title type="html">Pourquoi ne jamais versionner les tokens dans un repository</title><link href="https://jeckel-lab.fr/devops/2023/09/21/ne-pas-versionner-les-tokens-dans-git.html" rel="alternate" type="text/html" title="Pourquoi ne jamais versionner les tokens dans un repository" /><published>2023-09-21T00:00:00+00:00</published><updated>2023-09-21T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2023/09/21/ne-pas-versionner-les-tokens-dans-git</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2023/09/21/ne-pas-versionner-les-tokens-dans-git.html"><![CDATA[<p>Aujourd’hui, Git est devenu un incontournable dans tout projet de développement, indispensable à la gestion de version du code source et au travail en équipe.
Cependant, il y a de bonnes pratiques à avoir, des notions de sécurité, des erreurs à ne pas commettre.</p>

<p>L’une de ces erreurs que l’on retrouve hélas régulièrement est celui d’enregistrer dans Git des tokens d’api, bearer ou autre élément d’authentification.</p>

<p>Parfois volontaire, souvent par erreur, voyons pourquoi c’est une pratique à bannir de tout projet.</p>

<h2 id="1-violation-de-la-sécurité">1. Violation de la sécurité</h2>

<p>Le stockage de ces tokens est avant tout un problème de sécurité.</p>
<h3 id="exposition-des-informations-sensibles">Exposition des informations sensibles</h3>

<p>Lorsque l’on stocke des tokens / mots de passe / clés d’accès dans un repository Git, nous les exposons au public. Même si le repository est sécurisé et ouvert à un public restreint, l’ensemble de ce public va pouvoir accéder à ces informations.</p>

<p>Git est un système de gestion de version distribué, c’est-à-dire que chaque clone du repository contient non seulement la version en cours, mais aussi l’historique complet, y compris donc les commits précédents.</p>

<p>Pas conséquent, si des informations sensibles sont versionnées, même si elles sont supprimées par un commit correctif suivant reste à disposition de n’importe qui ayant accès au repository ou à l’un des clones.</p>

<h3 id="ouverture-au-piratage">Ouverture au piratage</h3>

<p>La présence de tokens et mots de passe dans un repository Git peut donc entraîner une violation de la sécurité. Comme vu précédemment, sans suppression explicite de l’ensemble des commits impactés, ces tokens restent présents dans l’historique et donc accessible pour quiconque ayant au moins clone du repository.</p>

<p>Il est très facile d’écrire un bot qui scanne un repository pour y trouver une signature de token ou de mot de passe.</p>

<p>Ce genre de faille de sécurité peut créer une ouverture permettant d’accéder ensuite à d’autres fonctionnalités de l’entreprise et avoir ainsi des conséquences graves.</p>

<p>La sécurité est une préoccupation constante pour les entreprises et doit l’être aussi pour les développeurs. Ce genre de compromission peut nuire gravement à la réputation de l’entreprise et à la confiance accordée par ces clients et utilisateurs.</p>

<h3 id="difficulté-de-révocation">Difficulté de révocation</h3>

<p>En cas de compromission des informations de sécurité, il devient urgent de pouvoir révoquer ces informations. Mais lorsque ces informations sont versionnés sur un repository Git, il devient difficile d’identifier tous les environnements, poste de développeur, machines de déploiement où ces informations sont conservées. 
Il est alors difficile de les révoquer efficacement.</p>

<h2 id="2-conflit-denvironnement">2. Conflit d’environnement</h2>

<p>D’une manière plus pragmatique, les tokens et mot de passe sont en général liés à l’intégration du projet à un outil extérieur à l’application.</p>

<p>Conserver ces informations dans le repository crée donc un lien fort entre ces applications et ne permet donc plus la possibilité d’avoir une configuration différente en fonction de l’environnement (Développement vs. Recette vs. Production).</p>

<h2 id="3-bonnes-pratiques">3. Bonnes pratiques</h2>

<p>Pour éviter ces risques, il faut donc éviter à tout prix de stocker ces informations dans un repository.</p>

<p>D’autres solutions plus sécurisées existent :</p>
<ul>
  <li>utiliser des variables d’environnements ou un fichier de configuration non versionné</li>
  <li>utiliser un gestionnaire de secrets comme HashiCorp Vault, AWS Secrets Manager</li>
  <li>il est aussi possible de versionner un vault dans lequel ces données sont hashées et ne sont disponibles qu’avec un mot de passe ou une passphrase (évidement, non versionné), possible avec Symfony Vault ou Ansible Vault.</li>
</ul>

<p>Les secrets seront mis à disposition de l’application lors de son déploiement sur l’environnement cible.</p>

<p>En conclusion, versionner des informations sensibles dans un repository git est une mauvaise habitude souvent réalisée pour les mauvaises raisons et qui apporte beaucoup plus de problème qu’elle ne résout de solutions.</p>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="git" /><category term="securité" /><category term="page-expert-php" /><category term="page-architecture" /><summary type="html"><![CDATA[Découvrez les risques de sécurité liés à la version des tokens, mots de passe et clés d'accès dans Git. Apprenez pourquoi cette pratique est à éviter et comment adopter de bonnes pratiques pour protéger vos informations sensibles.]]></summary></entry><entry><title type="html">8 Conseils pour faire votre conteneur docker NodeJs de production</title><link href="https://jeckel-lab.fr/devops/2018/02/08/conteneur-nodejs-en-production.html" rel="alternate" type="text/html" title="8 Conseils pour faire votre conteneur docker NodeJs de production" /><published>2018-02-08T00:00:00+00:00</published><updated>2018-02-08T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2018/02/08/conteneur-nodejs-en-production</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2018/02/08/conteneur-nodejs-en-production.html"><![CDATA[<p>Entre le développement et la production, on oublie trop souvent qu’il y a un gap à franchir. L’application sur laquelle on développe, même si l’on développe directement dans un conteneur docker ne peut être livrée telle quelle en production. Voici donc quelques conseils pour réussir un conteneur « production ready » en NodeJS.</p>

<h2 id="1-pas-de-variable-unique-pour-lenvironnement">1. Pas de variable unique pour l’environnement</h2>

<p>C’est une mauvaise pratique hélas assez courante en développement, avoir une variable d’environnement ou de configuration « <code class="language-plaintext highlighter-rouge">Environnement</code> » et tester ensuite dans le code si <code class="language-plaintext highlighter-rouge">Environnement = "PROD"</code> alors…<br />
Il faut au contraire multiplier les options de configuration pour ajuster chacun des cas en fonction de l’environnement. Cela vous permet d’une part d’avoir un code plus proche de la production et d’autre part de pouvoir activer / désactiver les fonctionnalités. Vous pouvez aussi multiplier les environnements à l’infini (pas de liste fini d’environnement, mais une liste infinie de combinaison de configuration possible).</p>

<h2 id="2-injecter-des-variables-denvironnement-pour-rendre-la-configuration-plus-dynamique">2. Injecter des variables d’environnement pour rendre la configuration plus dynamique</h2>

<p>Ok, maintenant que vous avez pleins d’options de configurations, il s’agit maintenant de pouvoir ajuster ces options sans avoir à reconstruire votre image à chaque fois. Docker permet d’injecter des variables d’environnement au moment d’instancier un nouveau conteneur depuis une image. Cela vous permet donc d’ajuster la configuration par rapport à chacun de vos besoins.<br />
Pour ma part, j’utilise dans chacun de mes projets un fichier <code class="language-plaintext highlighter-rouge">settings.js</code> qui ressemble à ça :</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Settings
 */</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
	<span class="na">express</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">port</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">EXPRESS_PORT</span> <span class="o">||</span> <span class="mi">3000</span><span class="p">,</span>
	<span class="p">},</span>
	<span class="na">postgres</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">user</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">POSTGRES_USER</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">admin</span><span class="dl">'</span><span class="p">,</span>
		<span class="na">host</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">POSTGRES_HOST</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">storage</span><span class="dl">'</span><span class="p">,</span>
		<span class="na">database</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">POSTGRES_DATABASE</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">database</span><span class="dl">'</span><span class="p">,</span>
		<span class="na">password</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">POSTGRES_PASSWORD</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">admin</span><span class="dl">'</span><span class="p">,</span>
		<span class="na">port</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">POSTGRES_PORT</span> <span class="o">||</span> <span class="mi">5432</span><span class="p">,</span>
	<span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Pour chaque option de configuration, il y a une variable d’environnement correspondante avec une valeur par défaut si cette variable n’a pas été définie.</p>

<h2 id="3-baser-limage-sur-alpine">3. Baser l’image sur Alpine</h2>

<p>Lorsque l’on construit un image Docker il est préférable de ne pas s’encombrer de choses inutiles qui pourraient alourdir ou ralentir le conteneur, voir y insérer des failles de sécurité.</p>

<p>Dans cette optique, je vous conseille, <strong>dans la mesure du possible</strong>, de baser votre image sur Alpine (<em>une version de linux extrêmement légère et réduite au minimum</em>) plutôt que sur Debian, cela permet d’éviter d’embarquer des applications/librairies inutiles et d’obtenir une image beaucoup inutilement alourdie.</p>

<h2 id="4-babel-webpack-grunt-importer-le-code-final-et-non-le-code-source">4. Babel, Webpack, Grunt… importer le code final et non le code source</h2>

<p>Si vous utilisez une librairie pour compiler/transpiler/packager votre code, il faut alors prévoir une étape de <strong>build</strong> du code lors de la création de l’image et ensuite supprimer le code source pour ne garder que le code de sortie du build.</p>

<p>De même, si vous utilisez des watchers qui recompile votre code à chaque modification (de type <code class="language-plaintext highlighter-rouge">nodemon</code>)<strong>,</strong> ceux-ci doivent être exclu de votre image. En effet ce type d’outils n’a d’utilité qu’en développement.</p>

<h2 id="5-utiliser-le-fichier-dockerignore">5. Utiliser le fichier .dockerignore</h2>

<p>Lors de la création de l’image, le fichier <code class="language-plaintext highlighter-rouge">.dockerignore</code> permet d’exclure automatiquement des fichiers des images Docker, c’est à dire que lors de l’utilisation d’instruction <code class="language-plaintext highlighter-rouge">ADD</code> ou <code class="language-plaintext highlighter-rouge">COPY</code> dans le <code class="language-plaintext highlighter-rouge">Dockerfile</code>, les fichiers spécifiés dans le fichier <code class="language-plaintext highlighter-rouge">.dockerignore</code> seront exclus de la copie.</p>

<p>On peut déjà y mettre systématiquement le dossier <code class="language-plaintext highlighter-rouge">node_modules</code> ainsi que tous les fichiers de tests, linter, etc..</p>

<h2 id="6-supprimer-les-tests-et-les-sources">6. Supprimer les tests et les sources</h2>

<p>Si vous utiliser une librairie pour packager votre application, supprimer les fichiers source une fois le code compilé, ça allègera votre image en évitant encore d’embarquer du code inutile.</p>

<p>Idem pour les fichiers de tests, surtout quand dans de nombreux cas (celons comment sont fait vos tests), on ne souhaite pas du tout que ceux-ci s’exécutent en production.</p>

<h2 id="7-supprimer-les-packages-systèmes-obsolètes">7. Supprimer les packages systèmes obsolètes</h2>

<p>Certaines librairies nodejs nécessitent d’installer des packages systèmes supplémentaires, dans certains cas, ces packages ne servent qu’à l’installation (compilation) de la librairie, ou au build de votre application. Vérifiez si vous en avez encore besoin ou pas une fois votre application fonctionnelle. S’ils ne sont plus requis, on supprime.</p>

<h2 id="8-pas-dimage-pour-le-front-react--angular--etc">8. Pas d’image pour le front (React / Angular / Etc.)</h2>

<p>Enfin, ça peut sembler une évidence, mais même si en développement, il est souvent pratique d’avoir un nodejs qui tourne en continue avec un watcher qui va mettre à jour le script en front à chaque update du code… En production, vous ne devriez avoir que des fichiers statiques. Et dans ce cas, vous n’avez pas besoin de Node du tout pour les servir au navigateur.</p>

<p>Préférer une application plus adaptée pour servir des fichiers statiques comme NGinx ou Apache, ce sera plus performant, et bien plus adapté.</p>

<h3 id="conclusion-et-bonus">Conclusion et bonus</h3>

<p>Pour conclure, il nous faut donc :</p>

<ul>
  <li>Un <code class="language-plaintext highlighter-rouge">.dockerignore</code> pour exclure les fichiers inutile</li>
  <li>Une étape build pour compiler notre image</li>
  <li>Une étape de construction de l’image de Run (celle qui va réellement partir en production)</li>
</ul>

<p>On peut le faire avec deux <code class="language-plaintext highlighter-rouge">Dockerfile</code> (un pour le build, et un pour le run qui se basera sur le résultat du premier), ou alors utiliser le double « <code class="language-plaintext highlighter-rouge">FROM</code> » disponible depuis quelques temps dans le <code class="language-plaintext highlighter-rouge">Dockerfile</code>.<br />
En <strong>bonus</strong> donc, Je vous partage un sample <code class="language-plaintext highlighter-rouge">Dockerfile</code> (et son fichier <code class="language-plaintext highlighter-rouge">.dockerignore</code>) que j’utilise comme base pour mes projets en NodeJS.<br />
<a href="https://github.com/jeckel/dockerfiles/tree/master/nodejs">Vous pourrez retrouver la dernière version sur github.</a></p>

<ul>
  <li>Le fichier <code class="language-plaintext highlighter-rouge">Dockerfile</code> à adapter :</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>###############################################################################
# Step 1 : Builder image
#

FROM node:9-alpine AS builder
LABEL maintainer="Julien MERCIER &lt;devci@j3ck3l.me&gt;"

# Define working directory and copy source

WORKDIR /home/node/app
COPY . .

# Install dependencies and build whatever you have to build
# (babel, grunt, webpack, etc.)

RUN npm install &amp;&amp; \
	npm run build

###############################################################################
# Step 2 : Run image
#

FROM node:9-alpine
ENV NODE_ENV=production
WORKDIR /home/node/app

# Install deps for production only

COPY ./package* ./
RUN npm install &amp;&amp; \
	npm cache clean --force

# Copy builded source from the upper builder stage

COPY --from=builder /home/node/app/dist ./dist

# Expose whatever port you need to expose
# And start
CMD npm start
</code></pre></div></div>

<ul>
  <li>Le fichier <code class="language-plaintext highlighter-rouge">.dockeringore</code> :</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Ignore all .* files except the ones required for build

.*
!.babelrc

# Ignore build directories and dependencies
# everything that will be re-generated by the install/build steps

lib
dist
node_modules

# Ignore dev configuration files

docker-compose.yml
Dockerfile
Makefile
nodemon.json

# Ignore documentation

*.md

# Ignore tests files

src/**/*.test.js
test
jest.config.json
</code></pre></div></div>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="nodejs" /><category term="docker" /><category term="page-ci" /><summary type="html"><![CDATA[Entre le développement et la production, on oublie trop souvent qu’il y a un gap à franchir. L’application sur laquelle on développe, même si l’on développe directement dans un conteneur docker ne peut être livrée telle quelle en production. Voici donc quelques conseils pour réussir un conteneur « production ready » en NodeJS.]]></summary></entry><entry><title type="html">Variables d’environnements et Nginx avec Docker</title><link href="https://jeckel-lab.fr/devops/2018/01/22/env-variables-nginx-docker.html" rel="alternate" type="text/html" title="Variables d’environnements et Nginx avec Docker" /><published>2018-01-22T00:00:00+00:00</published><updated>2018-01-22T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2018/01/22/env-variables-nginx-docker</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2018/01/22/env-variables-nginx-docker.html"><![CDATA[<p>Il existe une image docker Nginx officielle pour docker que l’on peut trouver sur <a href="https://hub.docker.com/_/nginx/">le docker hub</a>. C’est cool. Mais l’un des besoins courant avec docker est de pouvoir adapter légèrement la configuration de l’image par rapport à son environnement d’exécution (comment dialoguer avec les conteneurs voisins par exemple) en injectant des variables d’environnement.<br />
Mais voilà, pour des raisons de performances (et aussi un peu de sécurité), les variables d’environnement ne sont pas accessible dans les fichiers de configurations nginx.</p>

<p>Voici donc quelques petits tips pour s’en sortir.</p>

<h2 id="utiliser-le-script-envsubst-fourni-avec-limage-officielle">Utiliser le script <code class="language-plaintext highlighter-rouge">envsubst</code> fourni avec l’image officielle.</h2>

<p>Dans la documentation de l’image officielle, on trouve quelques petites lignes sur l’utilisation d’un script qui va venir faire un remplacement de variables dans un fichier “template” afin de générer le fichier final qui sera lui, chargé au démarrage du serveur.<br />
Grosso modo, ça ressemble à ça, dans le fichier de configuration vous entrez le nom de votre variable.</p>

<div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">listen</span> ${<span class="n">NGINX_PORT</span>};
</code></pre></div></div>

<p>Puis, lors du lancement du conteneur, il faut ajouter votre variable d’environnement, et modifier la commande par défaut du conteneur pour faire la substitution en amont du lancement d’Nginx<br />
En ligne de commande, ça donne ça :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="se">\</span>
	<span class="nt">-v</span> <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/mysite.template:/etc/nginx/conf.d/mysite.template:ro <span class="se">\</span>
	<span class="nt">-e</span> <span class="nv">NGINX_PORT</span><span class="o">=</span>80 <span class="se">\</span>
	nginx <span class="se">\</span>
	shell <span class="nt">-c</span> <span class="s2">"envsubst &lt; /etc/nginx/conf.d/mysite.template &gt; /etc/nginx/conf.d/default.conf &amp;&amp; nginx -g 'daemon off;'"</span>
</code></pre></div></div>

<p>Ok, c’est pas l’idéal, mais ça fonctionne (pour la version avec docker-compose, il suffit d’aller voir sur la page sur docker hub).<br />
Seulement voilà, ce joli script est mal foutu, en effet, au lieu de boucler sur les variables d’environnements et de ne remplacer que celles-ci, cet idiot remplace tout ce qui commence par un $ dans votre fichier de configuration… C’est d’autant plus idiot que NGinx propose <a href="https://nginx.org/en/docs/varindex.html">une jolie collection de variables</a> (accessibles, elles, dans le fichier de configuration) et qui commencent toutes par un $.<br />
Par conséquent, si dans votre fichier, vous avez une ligne de ce type :</p>

<div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span> $<span class="n">my_hostname</span>=${<span class="n">ENV_HOSTNAME</span>}
</code></pre></div></div>

<p>Après le passage du script de substitution, votre fichier de configuration risque bien de ressembler à ça:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set =my.host.com
</code></pre></div></div>

<p>Ne cherchez pas, j’ai testé pour vous, nginx n’aime pas du tout 🙁</p>

<h2 id="contourner-le-problème-avec-docker-compose">Contourner le problème avec <code class="language-plaintext highlighter-rouge">docker-compose</code></h2>

<p>Docker-compose permet déjà de modifier pas mal de chose qui peut nous permettre de nous passer tout simplement de variables d’environnements dans le conteneur au profit du fichier de configuration <code class="language-plaintext highlighter-rouge">docker-compose.yml</code>.</p>

<h3 id="rendre-dynamique-le-nom-de-domaine-dune-cible">Rendre dynamique le nom de domaine d’une cible</h3>

<p>Il est possible d’injecter de nouvelles lignes dans le fichier <code class="language-plaintext highlighter-rouge">/etc/hosts</code> d’un conteneur avec simplement des options de configurations de docker-compose. On peut alors, dans notre fichier de configuration, utiliser un nom de « host » fixe, qui pointera sur une IP définie dans le fichier <code class="language-plaintext highlighter-rouge">/etc/hosts</code> configuré via docker-compose :</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="na">nginx</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">nginx:1.13</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro</span>
<span class="na">		extra_hosts</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">target-host:my.host.com"</span>
</code></pre></div></div>

<p>Et comme il est possible d’<a href="https://docs.docker.com/compose/environment-variables/">injecter des variables d’environnement</a> dans la configuration de docker-compose :</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="na">nginx</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">nginx:1.13</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro</span>
<span class="na">		extra_hosts</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">target-host:${ENV_HOSTNAME}"</span>
</code></pre></div></div>

<h3 id="surcharger-le-fichier-de-configuration-de-nginx">Surcharger le fichier de configuration de Nginx</h3>

<p>L’autre solution est de surcharger complètement le fichier de configuration, soit le fichier complet, soit un fichier annexe qui sera inclus dans le fichier principal.<br />
Le chemin (local) de ce fichier pouvant être rendu dynamique grâce à la prise en charge des variables d’environnement de docker-compose.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="na">nginx</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">nginx:1.13</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro</span>
<span class="err">			</span><span class="s">- ./nginx/${EXTRA_FILE}:/etc/nginx/conf.d/extra.conf:ro</span>
</code></pre></div></div>

<h2 id="hacker-le-script-de-substitution">Hacker le script de substitution</h2>

<p>En recherchant ce problème, certains ont trouvé le moyen de hacker le script de substitution, je vous laisse le lien vers le post en question, et vous laisse juger vous-même :<br />
<a href="https://serverfault.com/questions/577370/how-can-i-use-environment-variables-in-nginx-conf">How can I use environment variables in Nginx.conf</a></p>

<h2 id="créer-votre-propre-image">Créer votre propre image</h2>

<p>La dernière solution reste toujours de surcharger l’image officielle et de créer votre propre image, avec les scripts et configurations répondant à vos besoins spécifiques.</p>

<p>Il existe sans doute de nombreuses autres solutions, et c’est une combinaison de celles-ci qui répondra sans doute à vos besoins. N’hésitez pas à partager les votre en commentaires.</p>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="docker" /><category term="nginx" /><category term="env" /><category term="page-expert-php" /><summary type="html"><![CDATA[Il existe une image docker Nginx officielle pour docker que l’on peut trouver sur le docker hub. C’est cool. Mais l’un des besoins courant avec docker est de pouvoir adapter légèrement la configuration de l’image par rapport à son environnement d’exécution (comment dialoguer avec les conteneurs voisins par exemple) en injectant des variables d’environnement. Mais voilà, pour des raisons de performances (et aussi un peu de sécurité), les variables d’environnement ne sont pas accessible dans les fichiers de configurations nginx.]]></summary></entry><entry><title type="html">Lire la saisie utilisateur sur un terminal en PHP</title><link href="https://jeckel-lab.fr/php/2018/01/03/saisie-utilistateur-terminal-php.html" rel="alternate" type="text/html" title="Lire la saisie utilisateur sur un terminal en PHP" /><published>2018-01-03T00:00:00+00:00</published><updated>2018-01-03T00:00:00+00:00</updated><id>https://jeckel-lab.fr/php/2018/01/03/saisie-utilistateur-terminal-php</id><content type="html" xml:base="https://jeckel-lab.fr/php/2018/01/03/saisie-utilistateur-terminal-php.html"><![CDATA[<p>Dans de nombreux languages, demander à l’utilisateur, pendant l’execution du programme de saisir des informations est chose aisée. Le PHP étant un langage de script initialement développer exclusivement pour un usage Web, il n’est pas prévu de commande permettant de mettre en pause le programme le temps que l’utilisateur fournisse l’information qui nous manques.<br />
Heureusement, si cette fonction n’existe pas, PHP fournis tous les outils pour le faire.</p>

<p>La première chose à savoir c’est que PHP fourni des “ressources” correspondant aux entrées et sorties du système linux :</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">php://stdin</code> correspondant à l’entré<a href="2017-12-20-grafana-docker-compose.markdown">2017-12-20-grafana-docker-compose.markdown</a>e standard</li>
  <li><code class="language-plaintext highlighter-rouge">php://stdout</code> correspondant à la sortie standard</li>
  <li><code class="language-plaintext highlighter-rouge">php://stderr</code> correspondant à la sortie d’erreur</li>
</ul>

<p>Ces ressources peuvent être utilisées via de nombreuses commandes PHP comme s’il s’agissait de fichiers.<br />
Ainsi, on peut écrire sur la sortie d’erreur avec le code suivant :</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$stderr</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s2">"php://stderr"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">);</span>
<span class="nb">fwrite</span><span class="p">(</span><span class="s2">"An error occured</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nv">$stderr</span><span class="p">);</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$stderr</span><span class="p">);</span>
</code></pre></div></div>

<p>Par conséquent, on peut donc lire sur l’entrée standard de la même manière.<br />
Pour rappel, l’entrée standard se comporte ici comme un fichier, il nous faut donc quand même rajouter un moyen pour que le programme ne lise pas l’entrée indéfiniment pour pouvoir reprendre sa course.<br />
La fonction <a href="http://php.net/fgets"><code class="language-plaintext highlighter-rouge">fgets</code></a> permet de lire « une ligne » dans un fichier, c’est-à-dire qu’il va retourner tous les caractères jusqu’au premier saut de ligne. Ce qui est idéal pour nous puisque l’utilisateur va pouvoir ainsi valider sa saisie avec la toucher [ENTER] de son clavier.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">readUserEntry</span><span class="p">():</span> <span class="kt">string</span> <span class="p">{</span>
	<span class="nv">$stdin</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s2">"php://stdin"</span><span class="p">,</span><span class="s2">"r"</span><span class="p">);</span>
	<span class="nv">$input</span> <span class="o">=</span> <span class="nb">fgets</span><span class="p">(</span><span class="nv">$stdin</span><span class="p">);</span>
	<span class="nb">fclose</span> <span class="p">(</span><span class="nv">$stdin</span><span class="p">);</span>
	<span class="k">return</span> <span class="nv">$input</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Voilà qui est pas mal pour une première version.<br />
Pour nous faciliter le travail, PHP a ajouté <a href="http://php.net/manual/en/features.commandline.io-streams.php">des constantes</a> permettant d’accéder directement aux flux d’entrée sortie, plus besoin de les ouvrir et de les fermer, ce sont des ressources toujours accessible.<br />
Ainsi, le même code se résume maintenant à une ligne :</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">readUserEntry</span><span class="p">():</span> <span class="kt">string</span> <span class="p">{</span>
	<span class="k">return</span> <span class="nb">fgets</span><span class="p">(</span><span class="no">STDIN</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>On va maintenant rendre notre fonction un peu plus sexy et plus propre en ajoutant une invite et en nettoyant la réponse reçue :</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">readUserEntry</span><span class="p">(</span><span class="kt">?string</span> <span class="nv">$invite</span> <span class="o">=</span> <span class="kc">null</span><span class="p">):</span> <span class="kt">string</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="k">empty</span><span class="p">(</span><span class="nv">$invite</span><span class="p">))</span> <span class="p">{</span>
		<span class="nb">printf</span><span class="p">(</span><span class="nv">$invite</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="nb">trim</span><span class="p">(</span><span class="nb">fgets</span><span class="p">(</span><span class="no">STDIN</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="php" /><category term="php" /><category term="console" /><category term="terminal" /><category term="cli" /><category term="page-expert-php" /><summary type="html"><![CDATA[Dans de nombreux languages, demander à l’utilisateur, pendant l’execution du programme de saisir des informations est chose aisée. Le PHP étant un langage de script initialement développer exclusivement pour un usage Web, il n’est pas prévu de commande permettant de mettre en pause le programme le temps que l’utilisateur fournisse l’information qui nous manques. Heureusement, si cette fonction n’existe pas, PHP fournis tous les outils pour le faire.]]></summary></entry><entry><title type="html">Pré-configurer Grafana avec Docker-Compose</title><link href="https://jeckel-lab.fr/devops/2017/12/20/grafana-docker-compose.html" rel="alternate" type="text/html" title="Pré-configurer Grafana avec Docker-Compose" /><published>2017-12-20T00:00:00+00:00</published><updated>2017-12-20T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2017/12/20/grafana-docker-compose</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2017/12/20/grafana-docker-compose.html"><![CDATA[<p><a href="https://grafana.com/">Grafana</a> est un excellent outil permettant de créer facilement des dashboards de monitoring en se branchant sur différentes sources de données. Ce projet est <a href="https://github.com/grafana/grafana">open-source</a> et disponible sous la forme d’un <a href="https://hub.docker.com/r/grafana/grafana/">conteneur docker</a>, ce qui lui permet d’être intégré directement dans d’autres projets de plus grande envergure. C’est par exemple ce que j’ai fait avec <a href="https://www.jeckel-lab.fr/omeglast/">Omeglast</a>.<br />
Pour faciliter son inclusion dans un projet, il est nécessaire d’avoir une solution pour initialiser Grafana directement avec une configuration propre à notre projet (accès aux sources de données, dashboards initialisés, etc.)<br />
Heureusement, Grafana avec quelques outils supplémentaires dispose de tous les éléments nécessaires.</p>

<h2 id="principe-général">Principe général</h2>

<p>Grafana utilise par défaut un base de donnée SQLite pour stocker l’ensemble de sa configuration, mais il est possible, via quelques paramètre d’initialisation, de remplacer cette base par une autre base de notre choix, extérieur, et dont on aura un total contrôle.<br />
Ensuite, il ne nous restera qu’à configurer une fois Grafana, de sauvegarder cette configuration dans un fichier, et de pouvoir ensuite initialiser automatique notre base avec ce fichier sauvegardé.<br />
Pour info, l’ensemble du code source de ce tutoriel est disponible <a href="https://github.com/jeckel/tutorials/tree/master/docker/2-pre-configure-grafana">sur github</a>.</p>

<h2 id="etape-1--installer-grafana-avec-docker-compose">Etape 1 : Installer Grafana avec docker-compose</h2>

<p>Pour commencer, vous devez avoir <a href="https://docs.docker.com/engine/installation/">docker</a> et <a href="https://docs.docker.com/compose/install/">docker-compose</a> d’installés, si ce n’est le cas, aller voir les tutoriels associés, c’est très simple.<br />
On va donc commencer par initialiser Grafana seul, en utilisant docker-compose. Il suffit de copier le code suivant dans un fichier <code class="language-plaintext highlighter-rouge">docker-compose.yml</code>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="na">grafana</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">grafana/grafana</span>
<span class="na">	ports</span><span class="pi">:</span>
<span class="err">		</span><span class="pi">-</span> <span class="s2">"</span><span class="s">3000:3000"</span>
</code></pre></div></div>

<p>On lance l’application via <code class="language-plaintext highlighter-rouge">docker-compose up</code>, et après quelques secondes d’initialisation, l’application est disponible via l’url <code class="language-plaintext highlighter-rouge">http://localhost:3000</code><br />
A ce stade, Grafana fonctionne, mais vous avez tout à configurer, la première chose à faire sera donc d’exporter la configuration à l’extérieur du conteneur pour la rendre persistante. Il existe plusieurs options, la première consiste à simplement créer un nouveau volume dans lequel sera enregistré cette configuration (c’est ce que vous trouverez dans la documentation de <a href="https://hub.docker.com/r/grafana/grafana/">grafana sur docker hub</a>), mais il existe une autre option, bien plus sexy.</p>

<h2 id="etape-2--ajouter-une-base-de-stockage-de-la-configuration">Etape 2 : Ajouter une base de stockage de la configuration</h2>

<p>En effet, en lisant la documentation de Grafana d’une part et de son image docker d’autre part, on découvre deux choses:</p>

<ol>
  <li>il est possible de configurer Grafana pour qu’il stocke sa configuration dans une base de données externe (comme PostgreSQL par exemple)</li>
  <li>il est possible de redéfinir toutes les variables du fichier de configuration via des variables d’environnement injectée via docker.</li>
</ol>

<p>Alors allons-y, modifions notre fichier <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> en conséquence:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2'</span>

<span class="na">volumes</span><span class="pi">:</span>
<span class="err">	</span><span class="na">grafana-pgdata</span><span class="pi">:</span>

<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="c1"># Grafana dedicated storage</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">grafana-storage</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">postgres:10-alpine</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">grafana-pgdata:/var/lib/postgresql/data</span>
<span class="na">		environment</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">POSTGRES_PASSWORD=password</span>
<span class="err">			</span><span class="s">- POSTGRES_USER=admin</span>
<span class="err">			</span><span class="s">- POSTGRES_DB=grafana</span>

<span class="err">	</span><span class="c1"># Grafana Server</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">grafana</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">grafana/grafana</span>
<span class="na">		ports</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">3000:3000"</span>
<span class="err">		</span><span class="na">environment</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">GF_DATABASE_TYPE=postgres</span>
<span class="err">			</span><span class="s">- GF_DATABASE_HOST=grafana-storage</span>
<span class="err">			</span><span class="s">- GF_DATABASE_NAME=grafana</span>
<span class="err">			</span><span class="s">- GF_DATABASE_USER=admin</span>
<span class="err">			</span><span class="s">- GF_DATABASE_PASSWORD=password</span>
</code></pre></div></div>

<p>Nous avons donc :</p>

<ul>
  <li>créé un volume pour y stocker la base de donnée de PosgreSQL « <code class="language-plaintext highlighter-rouge">grafana-pgdata</code>« </li>
  <li>créé un conteneur PostgreSQL initialisé avec une base « <code class="language-plaintext highlighter-rouge">grafana</code>« </li>
  <li>modifié la configuration de Grafana pour utiliser notre base PostgreSQL pour sa configuration</li>
</ul>

<p>Y a plus qu’à lancer notre commande <code class="language-plaintext highlighter-rouge">docker-compose up</code>.<br />
CRACK.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fail to initialize orm engine<span class="s2">" logger=sqlstore error="</span>Sqlstore::Migration failed err: dial tcp 172.23.0.2:5432: getsockopt: connection refused
</code></pre></div></div>

<p>Ok, ça plante, mais la raison est très simple, le premier lancement de PostgreSQL est assez long car il doit construire et initialiser la base de donnée, parallèlement, Grafana est beaucoup plus rapide, et donc n’arrivant pas à se connecter à PostgreSQL qui n’est pas encore disponible, s’arrête avec une erreur.<br />
Si vous lancer PostgreSQL seul en premier, et Grafana ensuite avec quelques secondes d’interval, tout fonctionne.</p>

<h2 id="etape-3--ajouter-un-healthcheck-et-séquencer-le-démarrage">Etape 3 : Ajouter un Healthcheck et séquencer le démarrage</h2>

<p>Le Healthcheck est un petit bout de script qui permet de vérifier qu’un service est opérationnel. Et c’est ce que nous allons utiliser.<br />
Nous allons donc ajouter un petit script qui va nous permettre d’attendre que PostgreSQL soit disponible avant de lancer Grafana.<br />
Sur PostgreSQL, il y a une commande <code class="language-plaintext highlighter-rouge">pg_isready</code> qui nous permet de savoir si le serveur est prêt à traiter des requêtes ou non, et pour le lancer via docker, ça donne :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose <span class="nb">exec </span>grafana-storage pg_isready
</code></pre></div></div>

<p>Ajoutons ça dans un script <code class="language-plaintext highlighter-rouge">start.sh</code> qui va nous séquencer le démarrage :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

docker-compose up <span class="nt">-d</span> grafana-storage
<span class="k">until </span>docker-compose <span class="nb">exec </span>grafana-storage pg_isready <span class="o">&gt;</span> /dev/null
<span class="k">do
	</span><span class="nb">printf</span> <span class="s1">'.'</span>
<span class="k">done
</span><span class="nb">printf</span> <span class="s1">'\n'</span>
docker-compose up
</code></pre></div></div>

<p>Cette fois-ci, même après avoir supprimer toutes les images, conteneurs et volumes, en lançant la commande <code class="language-plaintext highlighter-rouge">./start.sh</code> tout fonctionne correctement.</p>

<h2 id="etape-4--exporter-la-sauvegarde">Etape 4 : Exporter la sauvegarde</h2>

<p>Nous avons donc maintenant Grafana qui fonctionne correctement et PostgreSQL qui contient toute la configuration de Grafana.<br />
Ce dont nous avons maintenant besoin c’est de pouvoir extraire la configuration de cette base de donnée pour pouvoir la sauvegarder avec notre application et pour la réimporter plus tard, automatiquement à l’installation de notre futur projet.<br />
Nous allons d’abord modifier notre fichier <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> pour y ajouter un nouveau montage de dossier destiné à recevoir l’export de la base de données :</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2.1'</span>

<span class="na">volumes</span><span class="pi">:</span>
<span class="err">	</span><span class="na">grafana-pgdata</span><span class="pi">:</span>

<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="c1"># Grafana dedicated storage</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">grafana-storage</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">postgres:10-alpine</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">grafana-pgdata:/var/lib/postgresql/data</span>
<span class="err">			</span><span class="s">- ./export:/var/export</span>
<span class="na">		environment</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">POSTGRES_PASSWORD=password</span>
<span class="err">			</span><span class="s">- POSTGRES_USER=admin</span>
<span class="err">			</span><span class="s">- POSTGRES_DB=grafana</span>
<span class="na">		healthcheck</span><span class="pi">:</span>
<span class="err">			</span><span class="na">test</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">CMD"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">pg_isready"</span><span class="pi">]</span>
<span class="err">			</span><span class="na">interval</span><span class="pi">:</span> <span class="s">30s</span>
<span class="na">			timeout</span><span class="pi">:</span> <span class="s">1s</span>
<span class="na">			retries</span><span class="pi">:</span> <span class="m">5</span>

<span class="err">	</span><span class="c1"># Grafana Server</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">grafana</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">grafana/grafana</span>
<span class="na">		ports</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">3000:3000"</span>
<span class="err">		</span><span class="na">environment</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">GF_DATABASE_TYPE=postgres</span>
<span class="err">			</span><span class="s">- GF_DATABASE_HOST=grafana-storage</span>
<span class="err">			</span><span class="s">- GF_DATABASE_NAME=grafana</span>
<span class="err">			</span><span class="s">- GF_DATABASE_USER=admin</span>
<span class="err">			</span><span class="s">- GF_DATABASE_PASSWORD=password</span>
</code></pre></div></div>

<p>Nous allons rajouter un petit script <code class="language-plaintext highlighter-rouge">dump.sh</code> nous permettant de faire un dump de la base dans ce dossier :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

docker-compose <span class="nb">exec </span>grafana-storage sh <span class="nt">-c</span> <span class="s2">"pg_dump -U admin grafana &gt; /var/export/dbexport.sql"</span>
</code></pre></div></div>

<p>Maintenant, vous pouvez donc lancer votre application avec <code class="language-plaintext highlighter-rouge">./start.sh</code>, puis via l’interface web créer votre configuration (source de donnée, dashboards, etc.), et une fois terminé, exporter cette configuration avec le script <code class="language-plaintext highlighter-rouge">./dump.sh</code>.<br />
Le fichier généré est alors disponible dans le dossier <code class="language-plaintext highlighter-rouge">export/</code>.</p>

<h2 id="etape-5--charger-la-configuration-au-démarrage">Etape 5 : Charger la configuration au démarrage</h2>

<p>Voilà, nous avons sauvegardé la configuration, la dernière étape est de pouvoir charger cette configuration à la prochaine initialisation du projet.<br />
Pour cette partie, c’est la documentation du conteneur PostgreSQL qui va nous donner la solution. En effet, il possible de spécifier un montage de dossier supplémentaire (<code class="language-plaintext highlighter-rouge">/docker-entrypoint-initdb.d</code>) contenant un (ou plusieurs) script qui seront chargé lors du premier lancement du conteneur (à l’initialisation donc).<br />
Du coup, encore une petite modification de notre fichier <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> :</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2.1'</span>

<span class="na">volumes</span><span class="pi">:</span>
<span class="err">	</span><span class="na">grafana-pgdata</span><span class="pi">:</span>

<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="c1"># Grafana dedicated storage</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">grafana-storage</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">postgres:10-alpine</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">grafana-pgdata:/var/lib/postgresql/data</span>
<span class="err">			</span><span class="s">- ./export:/var/export</span>
<span class="err">			</span><span class="s">- ./initdb:/docker-entrypoint-initdb.d</span>
<span class="na">		environment</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">POSTGRES_PASSWORD=password</span>
<span class="err">			</span><span class="s">- POSTGRES_USER=admin</span>
<span class="err">			</span><span class="s">- POSTGRES_DB=grafana</span>
<span class="na">		healthcheck</span><span class="pi">:</span>
<span class="err">			</span><span class="na">test</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">CMD"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">pg_isready"</span><span class="pi">]</span>
<span class="err">			</span><span class="na">interval</span><span class="pi">:</span> <span class="s">30s</span>
<span class="na">			timeout</span><span class="pi">:</span> <span class="s">1s</span>
<span class="na">			retries</span><span class="pi">:</span> <span class="m">5</span>

<span class="err">	</span><span class="c1"># Grafana Server</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">grafana</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">grafana/grafana</span>
<span class="na">		ports</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">3000:3000"</span>
<span class="err">		</span><span class="na">environment</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">GF_DATABASE_TYPE=postgres</span>
<span class="err">			</span><span class="s">- GF_DATABASE_HOST=grafana-storage</span>
<span class="err">			</span><span class="s">- GF_DATABASE_NAME=grafana</span>
<span class="err">			</span><span class="s">- GF_DATABASE_USER=admin</span>
<span class="err">			</span><span class="s">- GF_DATABASE_PASSWORD=password</span>
</code></pre></div></div>

<p>Nous pouvons maintenant mettre notre fichier précédemment sauvegardé dans le dossier local <code class="language-plaintext highlighter-rouge">initdb/</code>, et de lancer notre script <code class="language-plaintext highlighter-rouge">./start.sh</code>.<br />
Attention de bien supprimer tous les conteneurs et volumes précédemment créé, sinon docker-compose réutilisera ce qui existe déjà et n’utilisera pas la nouvelle configuration. Vous pouvez faire ce nettoyage avec la commande <code class="language-plaintext highlighter-rouge">docker-compose down -v</code>.<br />
Et voilà, vous pouvez maintenant inclure ces fichiers dans votre projet pour avoir une instance de Grafana déjà configuré par rapport à vos besoins.</p>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="docker-compose" /><category term="docker" /><category term="grafana" /><category term="page-ci" /><summary type="html"><![CDATA[Grafana est un excellent outil permettant de créer facilement des dashboards de monitoring en se branchant sur différentes sources de données. Ce projet est open-source et disponible sous la forme d’un conteneur docker, ce qui lui permet d’être intégré directement dans d’autres projets de plus grande envergure. C’est par exemple ce que j’ai fait avec Omeglast. Pour faciliter son inclusion dans un projet, il est nécessaire d’avoir une solution pour initialiser Grafana directement avec une configuration propre à notre projet (accès aux sources de données, dashboards initialisés, etc.) Heureusement, Grafana avec quelques outils supplémentaires dispose de tous les éléments nécessaires.]]></summary></entry><entry><title type="html">Portainer, Nginx et docker-compose</title><link href="https://jeckel-lab.fr/devops/2017/12/14/portainer-nginx-docker-compose.html" rel="alternate" type="text/html" title="Portainer, Nginx et docker-compose" /><published>2017-12-14T00:00:00+00:00</published><updated>2017-12-14T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2017/12/14/portainer-nginx-docker-compose</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2017/12/14/portainer-nginx-docker-compose.html"><![CDATA[<p>Lorsque l’on a besoin d’aller un peu plus loin (sur un serveur de test par exemple) on peut vouloir rajouter un minimum de sécurité : restreindre à certaines IP, restreindre à un domaine particulier, etc.<br />
C’est que je vais vous montrer ici en ajoutant un serveur NGinx en front avec docker-compose.</p>

<p>Tout d’abord, nous allons devoir transcrire ce que nous avions fait dans le précédent article sous la forme d’un fichier de configuration <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> :</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2'</span>

<span class="na">volumes</span><span class="pi">:</span>
<span class="err">	</span><span class="na">portainer_data</span><span class="pi">:</span>

<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="na">portainer</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">portainer/portainer</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock</span>
<span class="err">			</span><span class="s">- portainer_data:/data</span>
<span class="na">		command</span><span class="pi">:</span> <span class="s">-H unix:///var/run/docker.sock</span>
<span class="na">		ports</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">9000:9000"</span>
<span class="err">		</span><span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
</code></pre></div></div>

<p>Et pour le lancer :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> docker-compose up <span class="nt">-d</span>
Starting portainer_portainer_1 ...
Starting portainer_portainer_1 ... <span class="k">done</span>
</code></pre></div></div>

<p>Comme avant, l’application reste accessible via http://localhost:9000<br />
Maintenant, nous allons rajouter un serveur NGinx en frontal, et nous allons donc le configurer en reverse proxy.<br />
Pour cette partie, nous allons devoir adapter l’image NGinx par défaut pour y include notre configuration spécifique.<br />
Premièrement, nous allons ajouter un fichier avec la configuration du reverse proxy NGinx <code class="language-plaintext highlighter-rouge">docker/portainer.conf</code> :</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">upstream</span> <span class="n">portainer</span> {
	<span class="n">server</span> <span class="n">portainer</span>:<span class="m">9000</span>;
}

<span class="n">server</span> {
	<span class="n">listen</span> <span class="m">80</span>;
	<span class="n">location</span> / {
		<span class="n">proxy_http_version</span> <span class="m">1</span>.<span class="m">1</span>;
		<span class="n">proxy_set_header</span> <span class="n">Connection</span> <span class="s2">""</span>;
		<span class="n">proxy_pass</span> <span class="n">http</span>://<span class="n">portainer</span>/;
	}

	<span class="n">location</span> /<span class="n">api</span>/<span class="n">websocket</span>/ {
		<span class="n">proxy_set_header</span> <span class="n">Upgrade</span> $<span class="n">http_upgrade</span>;
		<span class="n">proxy_set_header</span> <span class="n">Connection</span> <span class="s2">"upgrade"</span>;
		<span class="n">proxy_http_version</span> <span class="m">1</span>.<span class="m">1</span>;
		<span class="n">proxy_pass</span> <span class="n">http</span>://<span class="n">portainer</span>/<span class="n">api</span>/<span class="n">websocket</span>/;
	}
}
</code></pre></div></div>

<p>Dans notre fichier <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> nous avions nommé le service portainer, c’est donc sous ce nom que, dans le réseau crée par docker-compose, notre container sera accessible ( <code class="language-plaintext highlighter-rouge">http://portainer:9000</code>)<br />
A ce fichier, nous allons rajouter un fichier <code class="language-plaintext highlighter-rouge">docker/Dockerfile</code> qui va nous permettre de charger l’image NGinx par défaut et d’y inclure notre configuration à la place de la configuration d’origine :</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> nginx:alpine</span>

<span class="c"># Remove existing configuration</span>

<span class="k">RUN </span><span class="nb">rm</span> <span class="nt">-v</span> /etc/nginx/conf.d/<span class="k">*</span>

<span class="c"># Insert our portainer conf</span>

<span class="k">COPY</span><span class="s"> portainer.conf /etc/nginx/conf.d/portainer.conf</span>
</code></pre></div></div>

<p>Et maintenant, il nous reste à mettre à jour notre fichier <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> pour y ajouter les instruction pour construire notre container NGinx et le lier à notre Portainer :</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2'</span>

<span class="c1"># Configure a dedicated volume for storing Portainer's configuration</span>
<span class="c1">#</span>

<span class="na">volumes</span><span class="pi">:</span>
<span class="err">	</span><span class="na">portainer_data</span><span class="pi">:</span>

<span class="na">services</span><span class="pi">:</span>
<span class="err">	</span><span class="c1"># Configure Portainer service</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">portainer</span><span class="pi">:</span>
<span class="err">		</span><span class="na">image</span><span class="pi">:</span> <span class="s">portainer/portainer</span>
<span class="na">		volumes</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock</span>
<span class="err">			</span><span class="s">- portainer_data:/data</span>
<span class="na">		command</span><span class="pi">:</span> <span class="s">-H unix:///var/run/docker.sock</span>
<span class="na">		restart</span><span class="pi">:</span> <span class="s">always</span>

<span class="na">		networks</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">local</span>

<span class="err">	</span><span class="c1"># Configure NGinx proxy service</span>
<span class="err">	</span><span class="c1">#</span>
<span class="err">	</span><span class="na">nginx</span><span class="pi">:</span>
<span class="err">		</span><span class="na">build</span><span class="pi">:</span> <span class="s">docker/</span>
<span class="na">		ports</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s2">"</span><span class="s">80:80"</span>
<span class="err">		</span><span class="na">depends_on</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">portainer</span>
<span class="na">		restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">		networks</span><span class="pi">:</span>
<span class="err">			</span><span class="pi">-</span> <span class="s">local</span>

<span class="c1"># Link network to the docker bridge driver</span>
<span class="c1">#</span>
<span class="na">networks</span><span class="pi">:</span>
<span class="err">	</span><span class="na">local</span><span class="pi">:</span>
<span class="err">		</span><span class="na">driver</span><span class="pi">:</span> <span class="s">bridge</span>
</code></pre></div></div>

<p>Et voilà, maintenant, en lançant votre docker-compose, votre instance de portainer est accessible directement vi l’url <code class="language-plaintext highlighter-rouge">http://localhost/</code>.<br />
En réalité, le traffic passe maintenant à travers NGinx avant d’atteindre Portainer, on peut donc ajouter toute la configuration que l’on souhaite sur NGinx (paramétrer la route pour ne matcher que sur un sous-domaine particulier, ajouter un filtre par IP, etc…).</p>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="portainer" /><category term="docker" /><category term="nginx" /><category term="docker-compose" /><category term="page-ci" /><summary type="html"><![CDATA[Lorsque l’on a besoin d’aller un peu plus loin (sur un serveur de test par exemple) on peut vouloir rajouter un minimum de sécurité : restreindre à certaines IP, restreindre à un domaine particulier, etc. C’est que je vais vous montrer ici en ajoutant un serveur NGinx en front avec docker-compose. Tout d’abord, nous allons devoir transcrire ce que nous avions fait dans le précédent article sous la forme d’un fichier de configuration docker-compose.yml :]]></summary></entry><entry><title type="html">Configurer NGinx en “reverse-proxy” devant NodeJS</title><link href="https://jeckel-lab.fr/devops/2017/12/05/nginx-reverse-proxy-nodejs.html" rel="alternate" type="text/html" title="Configurer NGinx en “reverse-proxy” devant NodeJS" /><published>2017-12-05T00:00:00+00:00</published><updated>2017-12-05T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2017/12/05/nginx-reverse-proxy-nodejs</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2017/12/05/nginx-reverse-proxy-nodejs.html"><![CDATA[<p>Dans de nombreux projets en NodeJS, on utilise un server web (comme express par exemple) qui permet de servir les pages html/javascript/css etc… aussi bien qu’un Apache ou un NGinx.<br />
Aussi bien ? pas tout à fait, en réalité NodeJS sera très bien pour tout ce qui est dynamique, mais ne sera jamais aussi performant pour distribuer des fichiers statiques. D’autre part, de nombreuses fonctionnalités disponible nativement sur un serveur web dédié ne seront pas a re-développer sur une application NodeJS.</p>

<h2 id="mais-cest-quoi-un-reverse-proxy-">Mais c’est quoi un reverse proxy ?</h2>
<p>Le principe du reverse proxy est assez simple. On va installer un serveur web classique de type NGinx qui va répondre à toutes les requêtes sur le port 80. Puis on va lui dire que toutes les requêtes qui correspondent à un format particulier (un nom de domaine par exemple) vont être redirigées vers une autre application (notre application NodeJS).</p>

<h2 id="pourquoi-utiliser-un-reverse-proxy-devant-votre-application-nodejs-">Pourquoi utiliser un reverse proxy devant votre application NodeJS ?</h2>
<ol>
  <li>Pour laisser au serveur web le soin de s’occuper des fichiers statiques et se concentrer sur le dynamique</li>
  <li>Pour utiliser les fonctionnalités « build-in » du serveur web (load-balancing, cache, rate-limiting, error page, access control, etc.)</li>
  <li>Meilleure protection contre les attaques (type DoS)</li>
  <li>Avoir plusieurs applications NodeJS qui répondent « virtuellement » sur le même port (mais sur des routes différentes)</li>
</ol>

<h2 id="et-comment-on-fait-">Et comment on fait ?</h2>
<p>Dans notre exemple, nous allons donc utiliser NGinx, et supposer que nous voulons mettre application en NodeJS sur un autre sous-domaine.</p>
<ul>
  <li>Etape 1 : installer nginx… pour cette étape, je vous invite à lire <a href="https://duckduckgo.com/?q=installation+de+nginx">les innombrables tutoriels déjà présent sur le web</a>.</li>
  <li>Etape 2 : ajouter un fichier de configuration à NGinx dans <code class="language-plaintext highlighter-rouge">/etc/nginx/sites-available/api-exemple</code></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
	listen 80;
	server_name api.exemple.coom;

	location / {
		proxy_pass http://127.0.0.1:3000;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection 'upgrade';
		proxy_set_header Host $host;
		proxy_cache_bypass $http_upgrade;
	}
}
</code></pre></div></div>

<p>Ce fichier indique à NGinx que toutes les requêtes entrante sur le port 80 et sur le nom de domaine « api.exemple.com » seront redirigées vers l’adresse 127.0.0.1 et sur le port 3000 (là où se trouve notre application NodeJS).</p>
<ul>
  <li>Etape 3 : activer la configuration
Il ne reste plus qu’à activer cette configuration :</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /etc/nginx/sites-enabled
<span class="nb">sudo ln</span> <span class="nt">-s</span> /etc/nginx/sites-available/api-exemple
<span class="nb">sudo</span> /etc/init.d/nginx reload
</code></pre></div></div>

<p>Et voilà, vous pouvez maintenant accéder à votre application directement depuis http://api.exemple.com</p>
<h2 id="pour-aller-plus-loin">Pour aller plus loin..</h2>
<p>Vous avez maintenant un fichier de configuration NGinx en front de votre application Node, c’est dans ce fichier de configuration que vous allez pouvoir rajouter des options supplémentaires comme :</p>

<ul>
  <li>du rate-limiting (limite du nombre de requêtes par intervalles de temps)</li>
  <li>des routes custom pour que NGinx charge directement les fichiers *.js *.html… ou par dossiers</li>
  <li>etc.</li>
</ul>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="nginx" /><category term="nodejs" /><category term="page-architecture" /><summary type="html"><![CDATA[Apprenez à configurer NGinx en tant que reverse proxy pour une application NodeJS.]]></summary></entry><entry><title type="html">Tagger une image docker avec le nom de la branche git</title><link href="https://jeckel-lab.fr/devops/2017/12/05/tag-docker-image-avec-branche-git.html" rel="alternate" type="text/html" title="Tagger une image docker avec le nom de la branche git" /><published>2017-12-05T00:00:00+00:00</published><updated>2017-12-05T00:00:00+00:00</updated><id>https://jeckel-lab.fr/devops/2017/12/05/tag-docker-image-avec-branche-git</id><content type="html" xml:base="https://jeckel-lab.fr/devops/2017/12/05/tag-docker-image-avec-branche-git.html"><![CDATA[<p>Un petit tips en passant, il est souvent bien utile de pouvoir utiliser le nom de la branche git en cours pour builder une image docker.<br />
Personnellement, je l’utilise dans deux cas :</p>

<ul>
  <li>Lorsque j’ai plusieurs développements en cours (et j’ai donc besoin d’une image correspondant à chaque branches)</li>
  <li>Lorsque j’ai différentes branches correspondant à différentes versions de l’image (version différente de PHP ou de NodeJS par exemple)</li>
</ul>

<p>Du coup, la première étape est de récupérer le nom de la branche en cours :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git rev-parse <span class="nt">--abbrev-ref</span> HEAD
</code></pre></div></div>

<p>Et il ne reste plus qu’à le stocker le résultat dans une variable.<br />
Autre petit truc, si la branche est <code class="language-plaintext highlighter-rouge">master</code>, dans ce cas je rajoute un tag <code class="language-plaintext highlighter-rouge">latest</code> à mon image.<br />
Et le tout dans un Makefile :</p>

<pre><code class="language-Makefile">.PHONY build

IMAGE:=jeckel/image
TAG:=$(shell git rev-parse --abbrev-ref HEAD)

build:
	@docker build -t ${IMAGE}:${TAG} .
	@if [ ${TAG} = "master" ]; then \
		docker tag ${IMAGE}:${TAG} ${IMAGE}:latest; \
	fi
</code></pre>

<p>Il ne reste plus qu’à builder l’image :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make build
</code></pre></div></div>

<p><strong>Note :</strong> si vous voulez utiliser plutôt le hash du dernier commit, il suffit d’utiliser la commande suivante :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git rev-parse HEAD
</code></pre></div></div>]]></content><author><name>Julien Mercier-Rojas</name></author><category term="devops" /><category term="docker" /><category term="git" /><category term="page-ci" /><summary type="html"><![CDATA[Un petit tips en passant, il est souvent bien utile de pouvoir utiliser le nom de la branche git en cours pour builder une image docker. Personnellement, je l’utilise dans deux cas :]]></summary></entry></feed>