Erreur Client version is too old

Si vous utilisez Gitlab CI pour builder des images docker, vous obtenez cette erreur, ce qui bloque la CI :

ERROR: Error response from daemon: client version 1.43 is too old. Minimum supported API version is 1.44, please upgrade your client to a newer version: driver not connecting

Cela veut dire que le client docker utilisé par la CI est trop ancien (ici 1.43 au lieu de 1.44).

Si Gitlab utilise le docker présent sur le système, à vous de le mettre à jour sur le serveur.

Sinon, vous utilisez une image docker qui contient un client docker permettant de builder une image docker 🤯.
Dans ce cas, pour utiliser un client plus récent, vous devez utiliser une image plus récente.

Vous pouvez spécifier cette version directement dans le fichier .gitlab-ci.yml à votre étape de build :

docker-build-backend-base:
    stage: docker-build-base
    only:
        refs:
            - merge_requests
        changes:
            - .gitlab-ci.yml
    image: docker:29.0.0
    interruptible: true
    variables:
        DOCKER_BUILDKIT: 1
        DOCKER_DRIVER: overlay2
    before_script:
        - apk update && apk add make
    script:
        - docker build -t gitlab.mydomain.com:1234/group/project/backend/base:latest -f ./docker/image/php/base/Dockerfile ./docker/image/php/base

La ligne importante ici est image: docker:29.0.0, qui force l’utilisation de la version 29 de l’image. Cette image contient un client Docker >= 1.44.

Marque-page Organiser un espace de documentation technique

Il est souvent difficile de s’y retrouver dans un espace documentaire. Sur quelle page (ou document) se trouve l’information dont j’ai besoin ? Comment y accéder ?
En général, on peut contourner le problème avec une recherche performante et une bonne indexation.

Mais cela n’aide pas vraiment pour la contribution et la mise à jour de la documentation. Comment savoir si ce qu’on veut ajouter est déjà présent ?
De plus, selon ce dont qu’on recherche, on n’a pas forcément envie d’avoir la même granularité dans les résultats.
Par exemple, si on recherche comment se connecter au VPN d’un client, on n’a pas sans doute pas besoin d’un paragraphe qui nous explique ce qu’est un VPN et à quoi il sert. Mais peut-être que ce paragraphe est quand même utile pour un autre utilisateur novice ?

Comme l’organisation de la documentation est un problème récurrent, des gens se sont penchés sur la question. Certains ont proposé l’approche Diataxis (https://diataxis.fr).

Elle préconise de structurer sa documentation en 4 parties :

  • Tutoriels
  • Recettes (How-to guides)
  • Référence
  • Explication

Le lecteur se dirigera vers l’une ou l’autre des sections, selon s’il souhaite :

  • Apprendre/débuter
  • Effectuer une tâche précise
  • Avoir des informations
  • Comprendre des sujets de fond

Astuce Couleurs de lignes alternées avec Excel

Dans un fichier Excel, si la couleur des lignes alterne mais que vous n’êtes pas dans un tableau, c’est que la couleur a été spécifiée pour chaque ligne manuellement.
Si vous ajoutez de nouvelles lignes, elles ne porteront donc pas l’alternance de couleur.

Pour corriger toutes vos nouvelles lignes d’un coup :

  • Sélectionnez juste deux lignes qui se suivent, avec la bonne alternance de couleur
  • Cliquer sur le bouton Reproduire la mise en forme
  • Sélectionnez toutes les lignes pour lesquelles corriger la coloration

L’alternance devrait maintenant être correcte.

Astuce Ouvrir une application en tant qu’administrateur sous Windows

Sous Windows, la solution classique pour ouvrir une application en tant qu’administrateur, c’est d’utiliser sa souris :

  • Dans l’explorateur Windows, faites un clic-droit sur l’icône de l’application
  • Dans le menu contextuel qui apparaît, cliquer sur l’option « Exécuter en tant qu’administrateur ».

Note : parfois, cette option n’apparaît que si on maintient la touche Ctrl enfoncée lors du clic-droit.

Un autre raccourci clavier

Il existe une autre méthode, plus rapide, pour ouvrir les applications de la barre des tâches ou du menu Démarrer :

  • Maintenez les touches Windows + Ctrl + Shift
  • Cliquer sur l’icône de l’application à exécuter en tant qu’administrateur

Astuce Dupliquer une ligne n fois avec PostgreSQL

Par exemple, vous avez une donnée en base, et pour vos tests, vous en voudriez 10 000.

Il est possible d’utiliser cette boucle SQL pour faire 10 000 insertions :

-- Copie d’une ligne n fois
--
-- Paramètres modifiables :
--   - nb de fois (ici 10000)
--   - table concernée (ici my_table)
--   - liste des colonnes à copier (ici column_1, column_2, column_x)
--   - id de la ligne à copier (ici 1)

DO
$$
    DECLARE
        i integer;
    BEGIN
        FOR i IN 1..10000
            LOOP
                INSERT INTO public.my_table (column_1, column_2, column_x)
                SELECT column_1, column_2, column_x
                FROM public.my_table
                WHERE id = 1;
            END LOOP;
    END
$$ LANGUAGE plpgsql;

Pour générer cette requête avec toutes les colonnes de votre table, vous pouvez faire générer l’INSERT directement à PostgreSQL, avec la requête suivante :

-- Génération de la requête d’insertion avec toutes les colonnes sauf la colonne id (car auto-incrémentale)
--
-- Paramètres modifiables :
--   - table concernée (ici my_table)
--   - id de la ligne à copier (ici 1)
WITH cols AS (SELECT column_name, ordinal_position
              FROM information_schema.columns
              WHERE table_schema = 'public'
                AND table_name = 'my_table'
                AND column_name NOT IN ('id')
              ORDER BY ordinal_position)
SELECT format(
               'INSERT INTO %I.%I (%s) SELECT %s FROM %I.%I WHERE id = 1;',
               'public',
               'my_table', string_agg(quote_ident(column_name), ', '), string_agg(quote_ident(column_name), ', '),
               'public', 'my_table') AS sql

Un dernier problème se pose si certaines colonnes doivent contenir des données uniques.
Dans les INSERT précédents, on ne gère que le cas de la colonne id, supposée auto-incrémentale. Pour générer des données pour d’autres colonnes, remplacez la requête précédente par celle-ci :

-- Génération de la requête de copie avec toutes les colonnes sauf celle contenant des ID uniques
--
-- Paramètres modifiables :
--   - table concernée (ici my_table)
--   - liste des colonnes pour lesquelles générer des uuid uniques (ici 'uuid1', 'uuid2')
--   - id de la ligne à copier (ici 1)
WITH cols AS (SELECT column_name, ordinal_position
              FROM information_schema.columns
              WHERE table_schema = 'public'
                AND table_name = 'my_table'
                AND column_name NOT IN ('id', 'uuid1', 'uuid2')
              ORDER BY ordinal_position)
SELECT format(
               'INSERT INTO %I.%I (uuid1, uuid2, %s) SELECT gen_random_uuid(), gen_random_uuid(), %s FROM %I.%I WHERE id = 1;',
               'public',
               'my_table', string_agg(quote_ident(column_name), ', '), string_agg(quote_ident(column_name), ', '),
               'public', 'my_table') AS sql

Note : si les valeurs uniques ne sont pas des UUID, il faut rechercher si PostgreSQL propose d’autres fonctions de génération aléatoires que gen_random_uuid().

Lister les clés étrangères qui référencent une table MySQL

On a parfois besoin de supprimer une entrée en base de donnée, mais qui est potentiellement référencées dans plusieurs autres tables. Plutôt que d’exécuter la requête de suppression et de corriger/supprimer une par une les données qui référencent et interdisent la suppression, on peut rechercher toutes les références possibles dans les autres tables.

-- Liste de toutes les clés étrangères vers la colonne `id` de `table1`
SELECT *
FROM
    INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
    REFERENCED_TABLE_NAME = 'table1'
  AND REFERENCED_COLUMN_NAME = 'id';

Astuce Utiliser le débogage pas à pas avec Docker et PHPStorm

Docker simplifie beaucoup de problématiques d’environnement pour les développeurs.
Quand la configuration de Docker est correcte, un développeur peut lancer un projet rapidement sans avoir à installer la bonne version de PHP, de la base de données, etc.

Malheureusement, cela complexifie aussi la communication entre ce qui tourne sur votre système hôte (ex : Windows) et les services nécessaires au projet (typiquement : PHP).

Voici comment configurer PHPStorm pour utiliser le débogage pas à pas d’XDebug, quand PHP est dockerisé.

Prérequis

PHP avec xDebug

Avant de parler de débogage pas à pas, il faut déjà qu’XDebug soit installé dans votre image docker. Voici un exemple d’image Docker avec PHP 8.4 et XDebug :

FROM php:8.4-fpm-alpine3.20

# Paramétrage du chemin vers les sources dans le conteneur
ENV MOUNT_DIR /srv/php
RUN mkdir -p ${MOUNT_DIR}
WORKDIR ${MOUNT_DIR}

# Installation d’XDebug
RUN install-php-extensions xdebug
COPY config/xdebug.ini $PHP_INI_DIR/conf.d/

Elle utilise ce fichier de config xdebug.ini (dans le répertoire config/) qui sera injecté dans l’image :

;config/xdebug.ini
xdebug.mode = debug,develop,profile
xdebug.start_with_request = trigger
xdebug.client_host = host.docker.internal

On peut builder l’image en local avec une commande du type :

docker build . -t my-group/my-image

Débogage pas à pas

Objectif

Dans l’exemple qui suit, on supposera qu’on souhaite déboguer le fichier src/Controller/TestController.php, lorsqu’on accède à la page http://localhost:8000/test.

Fichier PHP à déboguer

L’exemple utilise Symfony, mais c’est la même chose pour un autre contexte PHP. C’est bien un fichier index.php qui est exécuté ici, et il se charge ensuite d’appeler la méthode number() du fichier TestController.php.

Notez le point rouge dans la marge à gauche (l. 18). Il représente un point d’arrêt : là où vous souhaitez arrêter l’exécution pour déboguer. Pour ajouter un point d’arrêt, cliquez dans la marge à gauche de la ligne de code juste avant laquelle vous souhaitez arrêter l’exécution pour déboguer.
Ici, l’exécution s’arrêtera donc juste avant d’exécuter la ligne 18.

Configuration

Dans PHPStorm, on peut activer le débogage pas à pas en cliquant sur le bouton « téléphone rouge », qui devient vert quand il est activé (aussi disponible en bas du menu Run : Start Listening for PHP Debug Connections).

Boutons de débogage

Mais avant, il faut cliquer sur la liste déroulante Current file à côté, puis sur Edit Configurations....

Nouvelle configuration de Run

Dans la fenêtre de configuration :

  • Cliquez sur le bouton Add
  • Sélectionnez PHP Remote debug
  • Nommez la configuration (ex : Docker)

Boutons de débogage

  • Cliquez sur le lien Validate
  • Sélectionnez le bouton radio Output phpinfo()
  • Créez un fichier php contenant uniquement phpinfo();
  • Affichez-le dans votre navigateur et copiez la source de la page (via Ctrl+U)
  • Collez le code HTML dans le champ texte de la fenêtre de configuration
  • Cliquez sur le bouton Validate.
    PHPStorm va valider une série d’éléments et indiquer si XDebug est bien opérationnel :

Validation de la présence d’XDebug

  • Cliquez sur le bouton Cancel.

Extension de navigateur

Pour simplifier le débogage, vous pouvez installer une extension de navigateur, qui ajoutera une variable dans le header des requêtes, pour indiquer au serveur qu’on souhaite utiliser le débogage pas à pas.

Il en existe plusieurs, comme Xdebug Helper.

Elles fonctionnent toutes plus ou moins de la même manière, en ajoutant un bouton pour activer/désactiver le débogage :

Bouton de l’extension de navigateur

Débogage

  • Cliquez sur le bouton « téléphone rouge » dans PHPStorm pour activer le débogage
  • Cliquez sur le bouton « insecte » (ajouté par l’extension) dans votre navigateur pour activer le débogage
  • Appelez votre page à déboguer.
    La page devrait rester bloquée en cours de chargement, et PHPStorm devrait afficher une fenêtre indiquant qu’il a capté un script à déboguer :

Débogage entrant

  • Cliquez sur le bouton Accept

Normalement, rien ne se passe et la page s’affiche normalement. PHPStorm affiche un message d’alerte, car il n’a pas réussi à faire le lien entre la requête et le fichier PHP concret à déboguer :

Message d’alerte

  • Cliquez sur le lien PHP|Servers dans le message (ou recherchez cette configuration depuis la fenêtre de Settings globale de PHPStorm)
  • Créez un nouveau server (ex : nommé localhost).
  • Par défaut, seulement le répertoire contenant le fichier index.php est mappé. Il faut ajouter le répertoire racine de votre application PHP.
    Dans le tableau, la colonne de gauche indique le chemin physique de votre application sur votre machine ( probablement un répertoire WSL si vous êtes sous Windows). Celle de droite indique le chemin de l’application à l’intérieur de votre conteneur docker (/srv/php correspond au chemin indiqué dans le Dockerfile précédent).

Paramétrage du mapping des fichiers

Normalement, tout est opérationnel. Vous pouvez recharger votre page de test et l’exécution devrait s’arrêter au niveau de votre point d’arrêt.

Dans la section basse de votre IDE, apparait la variable locale $randomNumbers, avec sa valeur courante :

Débogage de la variable $randomNumbers

Marque-page Utiliser les cookies en javascript

L’utilisation des cookies en javascript est à la fois simple et compliquée.

On aimerait une interface d’API un peu plus directe, pour pouvoir juste ajouter/modifier/supprimer un cookie.
Voici un exemple d’implémentation d’une telle API, en javascript puis en typescript.

Version javascript :

//cookie.js
export const ONE_DAY_IN_SECONDS = 24 * 60 * 60 * 1000;
export const SEVEN_DAYS_IN_SECONDS = 7 * ONE_DAY_IN_SECONDS;

/**
 * @param {string} name
 * @param {string} value
 * @param {number} expirationDuration Durée en secondes, ou -1 pour que le cookie soit supprimé à la fermeture de la page.
 */
export function setCookie(name, value, expirationDuration = SEVEN_DAYS_IN_SECONDS) {
    const date = new Date();
    date.setTime(date.getTime() + expirationDuration);

    const expirationPart = expirationDuration > 0
        ? ` expires=${date.toUTCString()};`
        : '';

    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)};${expirationPart}path=/`;
}

/**
 * @param {string} name
 * @param {string|null} defaultValue
 *
 * @return {string|null}
 */
export function getCookie(name, defaultValue = null) {
    const encodedCookie = document.cookie
        .split('; ')
        .find((cookie) => cookie.startsWith(encodeURIComponent(name) + '='))
        ?.split('=')?.[1];

    return encodedCookie !== undefined ? decodeURIComponent(encodedCookie) : defaultValue;
}

/**
 * @param {string} name
 */
export function deleteCookie(name) {
    const expirationDate = new Date('Thu, 01 Jan 1970 00:00:01 GMT');

    document.cookie = `${encodeURIComponent(name)}=; expires=${expirationDate.toUTCString()}; path=/`;
}

Version typescript :

//cookie.ts
export const ONE_DAY_IN_SECONDS = 24 * 60 * 60 * 1000;
export const SEVEN_DAYS_IN_SECONDS = 7 * ONE_DAY_IN_SECONDS;

/**
 * @param {number} expirationDuration Durée en secondes, ou -1 pour que le cookie soit supprimé à la fermeture de la page.
 */
export const setCookie = (name: string, value: string, expirationDuration: number = SEVEN_DAYS_IN_SECONDS) => {
    const date = new Date();
    date.setTime(date.getTime() + expirationDuration);

    const expirationPart = expirationDuration > 0
        ? ` expires=${date.toUTCString()};`
        : '';

    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)};${expirationPart}path=/`;
};

export const getCookie = (name: string, defaultValue: string | null = null) => {
    const encodedCookie = document.cookie
        .split('; ')
        .find((cookie) => cookie.startsWith(encodeURIComponent(name) + '='))
        ?.split('=')?.[1];

    return encodedCookie !== undefined ? decodeURIComponent(encodedCookie) : defaultValue;
};

export const deleteCookie = (name: string) => {
    const expirationDate = new Date('Thu, 01 Jan 1970 00:00:01 GMT');

    document.cookie = `${encodeURIComponent(name)}=; expires=${expirationDate.toUTCString()}; path=/`;
};

Erreur Routing et export statique de Next.js vers Azure Static Web Apps

Lorsqu’on fait un export statique d’une application Next.js pour l’héberger sur Azure Static Web Apps, l’application ne fonctionne que lorsqu’on accède à la racine de l’application (/). Les autres chemins (ex : /ma-page) retourne une erreur 404.

Pour que le chemin /ma-page fonctionne, il faut en fait rediriger vers /ma-page/index.html.

On peut faire ça pour toutes les pages automatiquement, via un peu de configuration.

  1. Modifiez le fichier next.config.mjs pour y ajouter trailingSlash: true. Exemple :

    /** @type {import('next').NextConfig} */
    const nextConfig = {
      output: 'export',
      trailingSlash: true,
    };
    
    export default nextConfig;
  2. Créez un fichier staticwebapp.config.json à la racine, destiné à Azure Static Web Apps :

    {
      "navigationFallback": {
        "rewrite": "index.html",
        "exclude": ["*.{png,jpg,gif,svg}", "*.css"]
      }
    }

Astuce Déployer un site statique vers Microsoft Azure

Le service Microsoft Azure permet d’héberger un site statique dans un espace de stockage. Voici comment réaliser le déploiement en lignes de commande.

Prérequis

  1. Installez la CLI Azure

    # pour Debian
    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

    Documentation d’installation : https://learn.microsoft.com/fr-fr/cli/azure/install-azure-cli

  2. Activez les sites web statiques dans Azure Storage :

Paramétrage Azure storage

Déploiement

Variables d’environnement à configurer

  • AZURE_APP_ID (ex : 8514903f-ed49-48b6-b5f8-bc4ff676fce3) :
  • AZURE_CLIENT_SECRET (ex : TYY5A~I2d19dffcf938ImHBeS~-.S4db2318T.) :
  • AZURE_TENANT_ID (ex : 44db2318-9218-41b4-b935-2d19dffcf938) :
  • AZURE_STORAGE_ACCOUNT (ex : stmystaticwebsite) :

    AZURE_STORAGE_ACCOUNT

export AZURE_APP_ID='8514903f-ed49-48b6-b5f8-bc4ff676fce3'
export AZURE_CLIENT_SECRET='TYY5A~I2d19dffcf938ImHBeS~-.S4db2318T.'
export AZURE_TENANT_ID='44db2318-9218-41b4-b935-2d19dffcf938'
export AZURE_STORAGE_ACCOUNT='stmystaticwebsite'

Déploiement

# Authentification
az login --service-principal -u $AZURE_APP_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID
# Suppression des fichiers statiques existants
az storage blob delete-batch \
  --account-name $AZURE_STORAGE_ACCOUNT --auth-mode login \
  --source '$web' --pattern '*'
# Envoi des nouveaux fichiers statiques
az storage blob upload-batch \
  --account-name $AZURE_STORAGE_ACCOUNT --auth-mode login \
  --destination '$web' -s my_static_app_dir

CDN

Au cas où Microsoft Azure CDN est activé pour servir le site statique, il faut penser à le purger.

Variables d’environnement à configurer

  • AZURE_RESOURCE_GROUP (ex : RG_MY_STATIC_WEBSITE) :

    AZURE_RESOURCE_GROUP

  • AZURE_CDN_PROFILE_NAME (ex : cdnpmystaticwebsite) :

    AZURE_CDN_PROFILE_NAME

  • AZURE_CDN_ENDPOINT_NAME (ex : cdnemystaticwebsite) : c’est le même qu’AZURE_CDN_PROFILE_NAME, mais avec cdne plutôt que cdnp en préfixe.

    export AZURE_RESOURCE_GROUP='RG_MY_STATIC_WEBSITE'
    export AZURE_CDN_PROFILE_NAME='cdnpmystaticwebsite'
    export AZURE_CDN_ENDPOINT_NAME='cdnemystaticwebsite'

Purge du CDN

az cdn endpoint purge \
  --resource-group $AZURE_RESOURCE_GROUP \
  --profile-name $AZURE_CDN_PROFILE_NAME \
  --endpoint-name $AZURE_CDN_ENDPOINT_NAME \
  --content-paths '/*'