Utiliser NSSM pour démarrer un agent Jenkins

Dans cet article, j’expliquerai comment utiliser NSSM pour démarrer un agent Jenkins sur Windows en tant que service. Jenkins est un outil d’intégration continue que j’utilise pour gérer mes projets sous git. L’application programmée en Java possède une interface web très intuitive. Un autre de ses avantages est que l’on peut distribuer la charge de la construction et du déploiement d’une application sur plusieurs agents.

Pour un agent Linux, j’utilise habituellement comme Launch method l’option Launch agents via SSH. Pour un agent Windows, c’est plutôt l’option Launch agent by connecting it to the master qui sera utilisée. Une fois l’agent configuré, nous serons confrontés à une page similaire à l’image suivante.

Jenkins Agent page
Jenkins Agent

La première option permet de télécharger une application JNLP (Java Network Launch Protocol) qui démarrera l’agent. Il est aussi possible de l’installer en tant que service. Par contre, mon anti-virus m’empêche de l’ouvrir et/ou de l’installer. C’est l’une des raisons qui m’a poussé à utiliser l’application NSSM. NSSM signifie Non-Sucking Service Manager et, comme son nom l’indique, est un gestionnaire de service « performant ». L’application possède une interface graphique pour installer, éditer ou supprimer un service créé à partir de NSSM. Par contre, dans cet article, c’est plutôt des lignes de commande que nous utiliserons.

Premièrement, pour me simplifier la tâche, j’ai installé nssm.exe dans le dossier C:\jenkins. C’est le Remote root directory dans lequel l’agent va opérer. Ensuite, on ouvre une fenêtre de terminal et on tape toutes ces commandes:

nssm install jenkinsagent "C:\jenkins\java\bin\java.exe"
nssm set jenkinsagent AppDirectory "C:\jenkins"
nssm set jenkinsagent AppParameters "-jar agent.jar -jnlpUrl http://server.com/ci/computer/Demo/jenkins-agent.jnlp -secret ba206ed36a928e773024ea46afdbfb8e33abd0de67f93c73213c4dcf229a1990 -workDir \"c:\jenkins\""
nssm set jenkinsagent DisplayName "Jenkins Agent Service"
nssm set jenkinsagent Description "A Jenkins agent."
nssm set jenkinsagent Start SERVICE_DELAYED_AUTO_START
nssm set jenkinsagent AppStdout "C:\jenkins\logs\nssm.log"
nssm set jenkinsagent AppStderr "C:\jenkins\logs\nssm.log"
nssm set jenkinsagent AppRotateFiles 1
nssm set jenkinsagent AppRotateBytes 1000000
nssm start jenkinsagent

L’agent nécessite d’être démarré par Java.exe, donc la ligne 1 doit être le chemin vers ce fichier. La ligne 3 est une copie de ce que vous allez trouver sur la page web de l’agent. La dernière ligne va démarrer le service. Après cette étape, l’agent devrait maintenant être disponible dans Jenkins.

Si vous désirez supprimer le service, c’est cette commande qu’il faut utiliser:

nssm remove jenkinsagent


Pour éditer un service déjà installé avec l’interface, on utilise cette commande:

nssm edit jenkinsagent

WiiBuilder 1.9.0

La version 1.9.0 de WiiBuilder est maintenant disponible. Cette nouvelle version permet l’utilisation de tous les points de code disponibles dans Unicode.

WiiBuilder 1.9.0
WiiBuilder 1.9.0

Au départ, je croyais qu’il suffisait seulement d’ajouter deux caractères supplémentaires dans la zone d’entrée de texte comme je l’avais déjà fait dans la version 1.8.1. Je me suis trompé, ce fut pas mal plus compliqué.

Le problème, c’est que dans C++Builder, wchar_t est sur deux octets. Ce qui veut dire que sa valeur maximale est de 65535. La plupart des points de code des émojis se trouvent en haut de cette valeur. Il fallait donc que je trouve comment encoder ces points de code en UTF-16. Heureusement, la page Wikipedia de UTF-16 explique comment faire. Puisque je suis un peu paresseux, j’ai finalement pris une partie du code sur ce site web. Voici ce que ça donne dans une fonction C++Builder.

String __fastcall CodePointToString(uint32_t ACodePoint)
{
    static const uint32_t LEAD_OFFSET = 0xD800 - (0x10000 >> 10);

    String Result;

    if(ACodePoint > 0xFFFF)
    {
        wchar_t wc[3];
        wc[0] = LEAD_OFFSET + (ACodePoint >> 10);
        wc[1] = 0xDC00 + (ACodePoint & 0x3FF);
        wc[2] = L'\0';

        Result = String(wc);
    }
    else
    {
        Result = wchar_t(ACodePoint);
    }

    return Result;
}

Git: Renommer en bloc des fichiers avec expression régulière

Dans cet article, je vais expliquer comment renommer en bloc des fichiers sous Git à partir d’une expression régulière. Récemment j’ai eu besoin de changer plusieurs fichiers de la forme YYYYMMDD.pdf vers YYYY-MM-DD.pdf, donc ajouter des tirets pour séparer les éléments d’une date.

Mon premier réflexe a été d’utiliser PowerRename qui fait partie des utilitaires de Microsoft PowerToys. Pour ceux qui ne connaissent pas, c’est un des projets à code source ouvert de Microsoft qui est disponible sur GitHub.

PowerRename avec expressions régulières
PowerRename avec expressions régulières

Le renommage des fichiers a fonctionné parfaitement, malheureusement Git n’est pas capable de reconnaitre les nouveaux fichiers comme étant les anciens qui sont renommés. Cela veut donc dire que l’historique serait complètement perdu. Il fallait que je trouve une autre solution.

Ma chasse à la commande magique venait de commencer. Comme tout bon développeur, j’ai passé pas mal de temps sur Stack Overflow. Je n’ai pas réussi à trouver exactement ce que je cherchais. Par contre, j’ai trouvé assez d’informations pour créer mon propre script:

#!/bin/sh
in_regex='^([0-9]{4})([0-9]{2})([0-9]{2})\.pdf$'
out_match='\1-\2-\3.pdf'

for file in $(git ls-files | grep -E $in_regex); do
    newname=`echo "$file" | sed -E "s/$in_regex/$out_match/"`
    git mv --verbose $file $newname
done

Je me suis servi de regular expressions 101 pour tester mon expression régulière à la ligne 2. Il est a noter que je me sers de l’option -E pour grep et sed dans le but d’utiliser les expressions régulières étendues. Le renommage est effectué par la commande git mv. N’oubliez pas de soumettre vos changements une fois le renommage complété.

Relâche scolaire 2021

C’est maintenant la semaine de relâche scolaire, donc toute la famille est en congé. Malheureusement, on est encore en plein milieu de la pandémie de COVID-19. On est donc un peu limités par nos activités. Heureusement, les jeux vidéos sont là pour nous divertir.

Il y a deux jours, nous avons recommencé le jeu Scribblenauts Unlimited sur Wii U à partir du début. Nous avons facilement terminé le jeu en utilisant notre créativité. J’ai appris que l’on pouvait se servir d’un « veuf vénéneux » pour faire une potion magique.

Je termine avec cette capture d’écran effectuée avec UsendMii qui nous montre Maxwell avec sa sœur Lily à la toute fin du jeu.

Scribblenauts Unlimited : Maxwell et Lily
Scribblenauts Unlimited : Maxwell et Lily

Piskel

Piskel est un éditeur en ligne gratuit pour créer des sprites animés et du pixel art. Je trouve que c’est déjà fantastique de pouvoir créer des animations dans un navigateur web, mais en plus le code source est disponible sur GitHub. L’application peut être utilisée à partir de cet URL : https://www.piskelapp.com

Dans notre famille, on l’utilise de façon périodique pour s’amuser, souvent pendant les vacances des enfants.

Voici des créations de mon fils au début de la pandémie pendant que les écoles étaient fermées :

L’explosion
Coronavirus

Voici une autre de ses créations pendant les vacances de Noël. Ici c’est du vrai pixel art car l’image originale est de 50×50 pixels :

Cochons

Ma seule création datant de 2018 pendant mes vacances d’été :

Skateboard

GitVersion dans Jenkins sous Linux

Dans un article précédent, j’avais expliqué comment utiliser GitVersion dans Jenkins sous Windows. Celui-ci traitera du même sujet, sauf que cette fois ci c’est sur Linux que l’application fonctionnera.

Je rappelle que GitVersion est une application qui sert à la génération de version automatique à partir d’un dépôt Git. Jenkins est un outil d’intégration continue programmé en Java. Ces deux projets à code source ouvert sont tous deux hébergés sur GitHub.

Pour se simplifier la vie, nous allons utiliser un conteneur Docker Linux pour démarrer l’application GitVersion. Toutes les propriétés de GitVersion serviront à générer des variables d’environnement. Ces variables pourront par la suite être utilisées à n’importe quel endroit dans le fichier Jenkinsfile:

stage('Version') {
	agent {
		docker {
			image 'gittools/gitversion:4.0.1-linux-debian-netcoreapp2.1'
			args '--entrypoint=\'\''
		}
	}
	steps {
		// Génération du fichier gitversion.properties
		sh 'dotnet /app/GitVersion.dll /output buildserver'
		// Enregistrement des variables d'environnement du fichier gitversion.properties
		script {
			readFile('gitversion.properties').split("\n").each { line ->
				el = line.split("=")
				env."${el[0]}" = (el.size() > 1) ? "${el[1]}" : ""
			}
		}
	}
}

Maintenant que vous avez ces informations de version, vous pouvez les utiliser pour générer la version de votre logiciel automatiquement.

Démarrer Code en mode WSL

J’adore l’IDE Visual Studio Code pour plusieurs raisons. Une de celles-ci est l’utilisation du Terminal bash qui nous permet d’utiliser des lignes de commande Linux à l’aide de Windows Subsystem for Linux (WSL). Comme si ce n’était pas déjà assez, une nouvelle extension permet dorénavant de démarrer complètement l’IDE dans l’environnement WSL. Il est donc possible de démarrer des tâches et de déboguer des applications directement sous Linux dans Windows.

Pour utiliser cette fonctionnalité, il faut démarrer Code et cliquer sur le bouton vert dans le coin en bas à gauche. Par la suite, vous choisissez de redémarrer le dossier en mode WSL. Vous devriez voir que le bouton d’accès rapide indique maintenant WSL et la distribution de Linux, si vous en avez plusieurs.

Visual Studio Code en mode WSL

Ouvrir l’IDE deux fois me semble une perte de temps, étant donné que la manière dont je préfère l’ouvrir est par le menu contextuel dans le dossier de mon projet. J’ai décidé d’ajouter un item supplémentaire pour me faciliter la vie.

Menu contextuel avec l’item supplémentaire

Pour faire cela il suffit d’éditer le registre de Windows. Pour simplifier les choses, voici le contenu du fichier .reg que j’utilise.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\VSCodeWSL]
@="Open with Code WSL"
"Icon"="C:\\Program Files\\Microsoft VS Code\\Code.exe"

[HKEY_CLASSES_ROOT\Directory\Background\shell\VSCodeWSL\command]
@="wsl.exe Code ."

Il suffit de créer un fichier avec l’extension .reg qui contient cette information, de le sauvegarder et de faire un double-clic dessus. Ceci devrait rendre disponible la nouvelle fonctionnalité. La commande wsl.exe et l’application Code.exe doivent être dans votre path pour que ceci fonctionne.

Lorsque l’item est sélectionné dans le menu, une fenêtre de terminal devrait apparaitre brièvement avant de démarrer Code.

Mettre à jour la médiathèque de Kodi

Kodi est un lecteur multimédia libre. Je l’utilise à partir de LibreELEC sur un Raspberry Pi. Au moment d’écrire ces lignes, la version 9 (Leia) est la plus récente. J’ai configuré Kodi pour aller lire des fichiers vidéos sur un ordinateur sur mon réseau par SMB. Donc, il arrive que j’ajoute des fichiers sur ce disque et que je doive mettre à jour manuellement Kodi pour afficher ces nouveaux fichiers. Dans cet article nous allons voir comment provoquer la mise à jour automatiquement à partir d’une application codée avec C++Builder.

Un collègue au travail m’a parlé que Kodi possède un API qui permet d’effectuer plusieurs tâches. Il l’utilise lui-même dans un logiciel de sa conception. Il m’a dit que Kodi Leia avait brisé une des fonctionnalités qu’il utilise. L’API HTTP ne fonctionnait plus pour mettre à jour la médiathèque. Effectivement, sur le Wiki de Kodi on peux y lire le texte suivant.

HTTP (does not work in v18 Leia)

Pour remédier à cette situation il a décidé d’invoquer l’application curl. En général je n’aime pas que mes applications aient des dépendances externes. J’ai donc creusé un peu pour comprendre comment mieux faire cette action.

Tout d’abord avant de débuter il faut activer l’option Autoriser le contrôle à distance via HTTP.

Kodi: Autoriser le contrôle à distance via HTTP

Dans l’image j’ai encerclé où se trouve le port que vous allez devoir utiliser.

Avant d’utiliser mon propre code, je vais tester la fonctionnalité. Sur la page HOW-TO:Remotely update library on y trouve une commande curl que je vais utiliser dans une fenêtre de terminal.

curl --data-binary '{"jsonrpc": "2.0", "method": "VideoLibrary.Scan", "id": "mybash"}' -H 'content-type: application/json;' http://libreelec:8080/jsonrpc

Voici la réponse que j’obtiens.

{"id":"mybash","jsonrpc":"2.0","result":"OK"}

Cette commande fonctionne bien, donc on ne devrait pas avoir de problème à communiquer avec Kodi. Une chose intéressante que l’on peut voir est qu’elle utilise l’étiquette jsonrpc. En cherchant un peu on comprend que Kodi à commencer à migrer de l’API HTTP vers un API JSON-RPC. Voici ce que la documentation dit à ce sujet.

JSON-RPC is a HTTP- and/or raw TCP socket-based interface for communicating with Kodi. It replaces the deprecated HTTP API, and offers a more secure and robust mechanism in the same format.

Voilà maintenant ce qui explique que l’API HTTP ne fonctionne pas complètement sur Leia.

Maintenant que l’on sait quoi faire, débutons l’application. La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TIdHTTP et un contrôle TButton.

Étant donnée que JSON sera utilisé, il faut ajouter cette ligne à votre fichier d’en-tête.

#include <System.JSON.hpp>

J’ai décidé de faire une petite classe minimaliste pour supporter JSON-RPC. Voici un autre bout de code à ajouter dans le fichier d’en-tête.

class TJsonRpc
{
public:
    __fastcall TJsonRpc() : Version("2.0") {}
    inline virtual __fastcall ~TJsonRpc() {}

    String Version;
    String Method;
    String Id;

    String __fastcall ToString()
    {
        TJSONObject* Obj = new TJSONObject();
        Obj->AddPair("jsonrpc", Version);
        Obj->AddPair("method", Method);
        Obj->AddPair("id", Id);
        TJSONObject* Params = new TJSONObject();
        Obj->AddPair("params", Params);
        const String Result = Obj->ToString();
        delete Obj;
        return Result;
    }
};

Finalement, il faut mettre ce code dans l’évènement OnClick du bouton.

    IdHTTP1->Request->ContentType = "application/json";

    TJsonRpc LJsonRpc;
    LJsonRpc.Method = "VideoLibrary.Scan";
    LJsonRpc.Id = "mybash";

    const String LUrl =  "http://libreelec:8080/jsonrpc";

    TStringStream* LData = NULL;
    try
    {
        LData = new TStringStream(LJsonRpc.ToString());

        const String LAnswer = IdHTTP1->Post(LUrl, LData);

        TJSONObject* Obj = static_cast<TJSONObject*>(TJSONObject::ParseJSONValue(LAnswer));
        try
        {
            TJSONPair* Pair;
            if((Pair = Obj->Get("error")) != NULL)
            {
                String LMessage;
                int LCode;
                TJSONObject* ErrorObj = static_cast<TJSONObject*>(Pair->JsonValue);
                if((Pair = ErrorObj->Get("message")) != NULL)
                {
                    TJSONString* Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    LMessage = AnsiDequotedStr(Answer->ToString(), '\"');
                }
                if((Pair = ErrorObj->Get("code")) != NULL)
                {
                    TJSONNumber* Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
                    LCode = Answer->AsInt;
                }
                throw Sysutils::Exception("Erreur " + String(LCode) + ": " + LMessage);
            }
        }
        __finally
        {
            delete Obj;
        }
    }
    __finally
    {
        delete LData;
    }

Même si je gère les erreurs d’API, il ne s’agit pas de code de production. Je ne vérifie pas que le ID de retour correspond à celui envoyé. Au lieu de tout mettre dans un évènement de bouton on aurait mieux fait de mettre dans une méthode séparée. On aurait pu aussi gérer la communication dans un thread séparé pour ne pas bloquer l’interface. Mais bon… l’idée c’était de montrer que la meilleure manière de communiquer avec Kodi est l’utilisation de JSON-RPC par HTTP.

J’espère que cet article vous sera utile.

WiiBuilder 1.8.2

La version 1.8.2 de WiiBuilder est maintenant disponible. Avant de mentionner la nouvelle fonctionnalité, je dois d’abord donner quelques explications.

Le toolchain wut qui fait maintenant partie de devkitPro permet de générer des fichiers RPX pour la Wii U. Un RPX est un fichier exécutable de format binaire. Il est généré à partir d’un fichier ELF temporaire. Il est similaire au format RPL (REL PLus), sauf qu’il possède un point d’entrée avec la fonction main().

L’application Homebrew Launcher, comme sont le nom l’indique, sert à démarrer des homebrews. Elle les charge à partir d’une carte SD ou directement à partir d’une connexion TCP/IP sur le port 4299. Depuis la version 1.4, les fichiers RPX peuvent être utilisés.

Maintenant, revenons à WiiBuilder. La nouvelle version permet d’envoyer des fichiers RPX à une Wii U qui roule l’application Homebrew Launcher. Une fois le fichier reçu, le homebrew sera démarré. Avant cette mise à jour, seuls les fichiers ELF pouvaient être chargés sur une Wii U.

WiiBuilder 1.8.2
La capture d’écran montre que le fichier boot.rpx a été envoyé à la Wii U avec succès.

Reverse proxy et Jenkins

Jenkins est un outil d’intégration continue que j’utilise sur un serveur Windows pour gérer mes projets sous git. L’application programmée en Java possède une interface web très intuitive. Pour l’utiliser à partir d’un site web commercial (.com) j’ai dû configurer IIS avec un reverse proxy. Jenkins est accessible par TLS grâce à Let’s Encrypt. Cette configuration semble parfaite, sauf que j’ai un petit problème. Lorsque j’accède à la section Manage Jenkins, un message d’erreur est affiché.

It appears that your reverse proxy set up is broken.
Avertissement à propos du reverse proxy dans Jenkins

Plusieurs guides existent en ligne pour corriger ce problème, mais aucun n’a fonctionné complètement avec mon environnement. Après avoir regardé des instructions pour Apache et Nginx j’ai compris qu’il fallait modifier les en-tête HTTP suivantes: X-Forwarded-Proto, X-Forwarded-Port et X-Forwarded-Host.

J’ai essayer de les ajouter comme Server Variables dans les règles de mon site web sur IIS, mais cela n’a pas fonctionné. En lisant la documentation de Microsoft j’ai réalisé que les variables devaient être en majuscule, que les tirets devaient être remplacés par des traits de soulignement et que le préfixe « HTTP_ » était nécessaire. Donc, voici ce que j’ai ajouté comme valeurs de remplacement:

  • HTTP_X_FORWARDED_PROTO = https
  • HTTP_X_FORWARDED_PORT = 443
  • HTTP_X_FORWARDED_HOST = {HTTP_HOST}

Résultat dans IIS:

IIS Inbound Rule for Jenkins
Capture d’écran des règles pour Jenkins dans IIS

Après avoir enregistré les changements, j’ai actualisé la page de gestion sur Jenkins et l’erreur est disparue.