Article
· Nov 30, 2023 9m de lecture

Diffusion continue de votre solution InterSystems à l'aide de GitLab - Partie IV : Configuration du CD

Dans cette série d'articles, j'aimerais présenter et discuter de plusieurs approches possibles pour le développement de logiciels avec les technologies d'InterSystems et GitLab. J'aborderai des sujets tels que:

  • Git 101
  • Flux Git (processus de développement)
  • Installation de GitLab
  • Flux de travail GitLab
  • Diffusion continue
  • Installation et configuration de GitLab
  • GitLab CI/CD

Dans le premier article, nous avons évoqué les notions de base de Git, les raisons pour lesquelles une compréhension approfondie des concepts de Git est importante pour le développement de logiciels modernes et la manière dont Git peut être utilisé pour développer des logiciels.

Dans le deuxième article, nous avons évoqué le flux de travail GitLab - un processus complet du cycle de vie du logiciel ainsi que Diffusion continue.

Dans le troisième article, nous avons évoqué l'installation et la configuration de GitLab et la connexion de vos environnements à GitLab

Dans cet article, nous allons enfin écrire une configuration sur CD.

Plan

Environnements

Tout d'abord, nous avons besoin de plusieurs environnements et de branches qui leur correspondent:

Environnement Branche Diffusion Qui peut valider Qui peut fusionner
Test master Automatique Développeurs  Propriétaires Développeurs  Propriétaires
Preprod (préproduction) preprod Automatique Personne Propriétaires
Prod (production) prod Semi-automatique (appuyez sur le bouton pour diffuser) Personne

Owners

Cycle de développement

A titre d'exemple, nous allons développer une nouvelle fonctionnalité en utilisant le flux GitLab et la livrer en utilisant le GitLab CD.

  1. Les fonctionnalités sont développées dans une branche des fonctionnalités.
  2. La branche des fonctionnalités est révisée et fusionnée dans la branche master.
  3. Après quelque temps (plusieurs fonctionnalités fusionnées), le master est fusionné dans le preprod
  4. Après quelque temps (tests d'utilisateurs, etc.), la préprod est fusionnée dans la prod

Voici à quoi il pourrait ressembler (j'ai marqué en cursive les parties qu'il faut développer pour le CD) :

  1. Développement et test
    • Le développeur valide le code de la nouvelle fonctionnalité dans une branche de fonctionnalité séparée
    • Une fois que la fonctionnalité est devenue stable, le développeur fusionne la branche de la fonctionnalité dans la branche master
    • Le code de la branche master est livré à l'environnement de test, où il est téléchargé et testé
  2. Diffusion dans l'environnement de Preprod
    • Le développeur crée une demande de fusion de la branche master vers la branche preprod
    • Le propriétaire du référentiel approuve la demande de fusion après un certain temps
    • Le code de la branche Preprod est diffusé dans l'environnement Preprod
  3. Diffusion dans l'environnement Prod
    • Le développeur crée une demande de fusion de la branche preprod vers la branche prod
    • Le propriétaire du référentiel approuve la demande de fusion après un certain temps
    • Le propriétaire du référentiel appuie sur le bouton "Deploy" (Déployer)
    • Le code de la branche prod est diffusé dans l'environnement Prod

Ou le même mais sous une forme graphique :

Application

Notre application se compose de deux parties:

  • API REST développée sur la plateforme InterSystems
  • Application web JavaScript client

Étapes

À partir du plan ci-dessus, nous pouvons déterminer les étapes à définir dans notre configuration de diffusion continue :

  • Téléchargement - pour importer le code côté serveur dans InterSystems IRIS
  • Test - pour tester le code client et le code serveur
  • Paquet - pour construire le code client
  • Déploiement - pour "publier" le code client à l'aide d'un serveur web

Voici à quoi cela ressemble dans le fichier .gitlab-ci.yml de configuration :

stages:
  - load
  - test
  - package
  - deploy

Scripts

Téléchargement

Ensuite, définissons les scripts. Documentation des scripts. Tout d'abord, définissons un serveur de téléchargement de scripts qui permet le téléchargement du code côté serveur :

load server:
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test
  stage: load
  script: iris session IRIS "##class(isc.git.GitLab).load()"

Que se passe-t-il ici?

  • load server est un nom de script
  • ensuite, nous décrivons l'environnement dans lequel ce script s'exécute
  • only: master - indique à GitLab que ce script est exécuté uniquement lorsqu'il y a un commit sur la branche master
  • tags: test indique que ce script doit être exécuté uniquement sur un système d'exécution qui a la balise de test
  • stage indique l'étape d'un script
  • script définit le code à exécuter. Dans notre cas, nous appelons le téléchargement de classmethod à partir de la classe isc.git.GitLab

Remarque importante

Pour InterSystems IRIS, remplacez csession par iris session.

Pour une utilisation sous Windows: irisdb -s ../mgr -U TEST "##class(isc.git.GitLab).load()

 

Maintenant, écrivons la classe isc.git.GitLab correspondante. Tous les points d'entrée de cette classe ressemblent à ceci :

ClassMethod method()
{
    try {
        // code
        halt
    } catch ex {
        write !,$System.Status.GetErrorText(ex.AsStatus()),!
        do $system.Process.Terminate(, 1)
    }
}

Notez que cette méthode peut se terminer de deux façons :

  • en arrêtant le processus en cours - ce qui est enregistré dans GitLab comme une réussite
  • en appelant  $system.Process.Terminate - ce qui termine le processus de manière anormale et GitLab l'enregistre comme une erreur

Enfin, voici notre code de téléchargement :

/// Effectuer un téléchargement complet
/// Effectuer ##class(isc.git.GitLab).load()
ClassMethod load()
{
    try {
        set dir = ..getDir()
        do ..log("Importing dir " _ dir)
        do $system.OBJ.ImportDir(dir, ..getExtWildcard(), "c", .errors, 1)
        throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error")
        
        halt
    } catch ex {
        write !,$System.Status.GetErrorText(ex.AsStatus()),!
        do $system.Process.Terminate(, 1)
    }
}

Deux méthodes utilitaires sont appelées :

  • getExtWildcard - pour obtenir une liste des extensions de fichiers pertinentes
  • getDir - pour obtenir le répertoire du référentiel

Comment obtenir le répertoire ?

Lorsque GitLab exécute un script, il spécifie d'abord un grand nombre de variables d'environnement. La première est CI_PROJECT_DIR - Le chemin complet où le référentiel est cloné et où la tâche est exécutée. Nous pouvons l'obtenir dans la méthode getDir sans problème :

ClassMethod getDir() [ CodeMode = expression ]
{
##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR"))
}


Tests

Voici le script de test:

load test:
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test
  stage: test
  script: iris session IRIS "##class(isc.git.GitLab).test()"
  artifacts:
    paths:
      - tests.html

Quels sont les changements ? Le nom et le code du script, bien sûr, mais l'artefact a également été ajouté. Un artefact est une liste de fichiers et de répertoires qui sont attachés à une tâche une fois qu'elle s'est achevée avec succès. Dans notre cas, une fois les tests terminés, nous pouvons générer une page HTML redirigeant vers les résultats des tests et la rendre disponible sur GitLab. 

Remarquez qu'il y a beaucoup de copier-coller de l'étape de téléchargement - l'environnement est le même, les parties du script telles que les environnements peuvent être étiquetées séparément et attachées à un script. Définissons l'environnement de test :

.env_test: &env_test
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test

Maintenant, notre script de test ressemble à ceci :

load test:
  <<: *env_test
  script: iris session IRIS "##class(isc.git.GitLab).test()"
  artifacts:
    paths:
      - tests.html

Ensuite, exécutons les tests à l'aide duframework UnitTest.

/// Exécuter ##class(isc.git.GitLab).test()
ClassMethod test()
{
    try {
        set tests = ##class(isc.git.Settings).getSetting("tests")
        if (tests'="") {
            set dir = ..getDir()
            set ^UnitTestRoot = dir
            
            $$$TOE(sc, ##class(%UnitTest.Manager).RunTest(tests, "/nodelete"))
            $$$TOE(sc, ..writeTestHTML())
            throw:'..isLastTestOk() ##class(%Exception.General).%New("Tests error")
        }
        halt
    } catch ex {
        do ..logException(ex)
        do $system.Process.Terminate(, 1)
    }
}

Le paramètre Tests, dans ce cas, est un chemin relatif à la racine du référentiel où sont stockés les tests unitaires. S'il est vide, les tests sont ignorés. La méthode writeTestHTML est utilisée pour produire du html avec une redirection vers les résultats des tests :

ClassMethod writeTestHTML()
{
    set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read()
    set text = $replace(text, "!!!", ..getURL())
    
    set file = ##class(%Stream.FileCharacter).%New()
    set name = ..getDir() _  "tests.html"
    do file.LinkToFile(name)
    do file.Write(text)
    quit file.%Save()
}

ClassMethod getURL()
{
    set url = ##class(isc.git.Settings).getSetting("url")
    set url = url _ $system.CSP.GetDefaultApp("%SYS")
    set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL")
    quit url
}

ClassMethod isLastTestOk() As %Boolean
{
    set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result)
    for i=1:1:in.TestSuites.Count() {
        #dim suite As %UnitTest.Result.TestSuite
        set suite = in.TestSuites.GetAt(i)
        return:suite.Status=0 $$$NO
    }
    quit $$$YES
}

XData html
{
<html lang="en-US">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="refresh" content="0; url=!!!"/>
<script type="text/javascript">
window.location.href = "!!!"
</script>
</head>
<body>
If you are not redirected automatically, follow this <a href='!!!'>link to tests</a>.
</body>
</html>
}

Paquet

Notre client est une simple page HTML:

<html>
<head>
<script type="text/javascript">
function initializePage() {
  var xhr = new XMLHttpRequest();
  var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/version";
  xhr.open("GET", url, true);
  xhr.send();
  xhr.onloadend = function (data) {
    document.getElementById("version").innerHTML = "Version: " + this.response;
  };
  
  var xhr = new XMLHttpRequest();
  var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/author";
  xhr.open("GET", url, true);
  xhr.send();
  xhr.onloadend = function (data) {
    document.getElementById("author").innerHTML = "Author: " + this.response;
  };
}
</script>
</head>
<body  onload="initializePage()">
<div id = "version"></div>
<div id = "author"></div>
</body>
</html>

Et pour le construire, nous devons remplacer ${CI_ENVIRONMENT_URL} avec sa valeur. Bien sûr, une application réelle nécessiterait probablement npm, mais ce n'est qu'un exemple. Voici le script :

package client:
  <<: *env_test
  stage: package
  script: envsubst < client/index.html > index.html
  artifacts:
    paths:
      - index.html

Déploiement

Enfin, nous déployons notre client en copiant index.html dans le répertoire racine du serveur web.

deploy client:
  <<: *env_test
  stage: deploy
  script: cp -f index.html /var/www/html/index.html

C'est tout !

Plusieurs environnements

Que faire si vous devez exécuter le même (ou similaire) script dans plusieurs environnements ? Les parties de script peuvent également être des étiquettes. Voici un exemple de configuration qui charge le code dans les environnements de test et de préprod :

stages:
  - load
  - test

.env_test: &env_test
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test
    
.env_preprod: &env_preprod
  environment:
    name: preprod
    url: http://preprod.hostname.com
  only:
    - preprod
  tags:
    - preprod

.script_load: &script_load
  stage: load
  script: iris session IRIS "##class(isc.git.GitLab).loadDiff()"

load test:
  <<: *env_test
  <<: *script_load

load preprod:
  <<: *env_preprod
  <<: *script_load

Nous pouvons ainsi éviter de copier-coller le code.

La configuration complète du CD est disponible ici. Il suit le plan initial de déplacement du code entre les environnements de test, de préproduction et de production.

Conclusion

La diffusion continue peut être configurée pour automatiser tout flux de développement nécessaire.

Liens

Prochaine étape

Dans le prochain article, nous créerons une configuration CD qui s'appuie sur le conteneur Docker InterSystems IRIS.

Discussion (0)0
Connectez-vous ou inscrivez-vous pour continuer