Initiation aux pipelines Devops Yaml – VI

Cet article va être un peu plus sophistiqué que les précédents. Nous allons monter un pipeline complet CI/CD.

Au programme donc:

  • Quelques techniques de base
  • Création d’un artefact
  • Création d’un tag
  • Gestion de la version de l’artefact
  • Pipeline CI

Dans cet article nous allons construire le pipeline CI (Continuous Integration) donc la finalité est de créer un artefact réutilisable.

Le pipeline CD (Continuous Delivery) sera abordé dans l’article qui suit.

Pourquoi un artefact?

L’objectif de tout pipeline CI est de créer un package immutable. C’est ce package qui servira de base pour le déploiement dans les divers environnements (DEV, UAT, PROD…). L’idée est d’être certain que ce qui est déployé dans un environnement est identique à ce qui a été validé dans l’environnement précédent, même si entre temps le source évolue.

Cette fois nous allons résoudre le problème via un artefact Devops. Nous verrons plus tard une autre méthode: création d’une task personnalisée.

Environnement

On aura besoin de deux repositories appelés respectivement Tuto-Deploy et Tuto-DeployBuild. Il faudra également créer un Service Connexion comme démontré durant l’opus III de cette série.

On aura aussi besoin d’un script capable de déployer les ressources Azure dont on a besoin. Voici la première version: 

$resourceGroup = "RG2"
$location = "westeurope"
$storageName = "stordemo15"

# création d'un RG
New-AzResourceGroup -Name $resourceGroup -Location $location -Force

# création d'un compte de stockage
New-AzStorageAccount -ResourceGroupName $resourceGroup `
  -Name $storageName `
  -Location $location `
  -SkuName Standard_LRS `
  -Kind StorageV2 

 

Il est hébergé dans le fichier Orchestrateur.ps1 déployé dans le repository Tuto-Deploy:

2021-06-04_21-49-09

 

Quelques préliminaires

Avant d’entrer dans le vif du sujet, on va faire étudier deux exemples pour démontrer quelques techniques.

Tout dans le même projet

Pour le déployer on aura besoin d’un script Yaml:

trigger:
- main
 
pool:
  vmImage: windows-latest
 
steps:
- task: AzurePowerShell@5
  displayName: 'Orchestrateur'
  inputs:
    azureSubscription: 'ToAzureCnx'
    ScriptType: 'FilePath'
    ScriptPath: '$(System.DefaultWorkingDirectory)/orchestrateur.ps1'
    azurePowerShellVersion: 'LatestVersion'

Nous allons le déposer dans le même repository que le script PowerShell:

2021-06-04_22-49-33

Notez la façon dont on récupère le fichier Yaml dans l’arborescence projet à l’aide de la variable prédéfinie DefaultWorkingDirectory. Notez également l’utilisation d’un Service Connexion pour permettre à Devops de se connecter à Azure.

Vous pouvez relire l’opus III de cette série pour savoir comment le créer.

Il ne reste alors plus qu’à vérifier que le déploiement fonctionne!

Cette solution marche bien et c’est certainement ce que je recommanderai pour un petit projet. Mais il peut sembler meilleur de séparer le pipeline des scripts, ne serait-ce que pour les rendre réutilisables.

Repository séparés

On va travailler sur deux repositories:

  1. Tuto-Deploy
    script PowerShell de création des ressources, l’orchestrateur se trouve dans ce projet
  2. Tuto-DeployBuild
    Pipeline Yaml

S’ils ne sont pas créés, c’est le moment!

La situation initiale de Tuto-Deploy est la suivante:

2021-06-16_21-57-38

C’est cet orchestrateur que l’on va versionner et pousser dans un artefact.

On déplace le pipeline dans le repository Tuto-DeployBuild et on le transforme un petit peu:

name: $(TeamProject)_$(Build.DefinitionName) CI $(Rev:.r)

trigger:
- none
 
pool:
  vmImage: windows-latest


resources:
  repositories:
    - repository: TutoDeploy
      ref: main
      type: git
      name: Tuto-Deploy

jobs:
   
 - job: Clean
   displayName: Nettoyage
   steps:
   - task: AzurePowerShell@5
     displayName: 'Orchestrateur'
     continueOnError: false
     inputs:
      azureSubscription: 'ToAzureCnx'
      ScriptType: 'inlineScript'
      inline: Remove-AzResourceGroup -name RG2 -Force -ErrorAction SilentlyContinue
      azurePowerShellVersion: 'LatestVersion'

 - job: checkout
   dependsOn: Clean
   steps:
   - checkout: self
   - checkout: TutoDeploy

 - job: Deploy
   displayName: Déploie les ressources Azure
   dependsOn: 
   - Clean
   - checkout
   steps:
   - task: AzurePowerShell@5
     displayName: 'Orchestrateur'
     condition: succeeded()
     inputs:
      azureSubscription: 'ToAzureCnx'
      ScriptType: 'FilePath'
      ScriptPath: .\Tuto-Deploy\orchestrateur.ps1
      azurePowerShellVersion: 'LatestVersion'

Analysons ce script.

On déclare un objet repository puisque cette fois le script n’est pas dans le même repository que le pipeline.

La première étape effectue un nettoyage en supprimant le groupe de ressources pour le redéployer ensuite.

On fait ensuite un checkout pour descendre le projet dans le répertoire de travail de l’agent de build. Checkout descend dans un répertoire \s. Plus précisément:

D:\a\1\s

On peut retrouver ce répertoire en lisant la variable $(Build.SourcesDirectory) qui pointe sur le répertoire où le code source est téléchargé.

Checkout créé dans  téléchargement arrive dans ce répertoire:

D:\a\1\s\Tuto-Deploy

2021-06-13_15-59-54

Par défaut, self est le repository dans lequel se trouve le pipeline. Pour des raisons que j’ignore, il est obligatoire d’ajouter un checkout: self dans le cas de checkout multiple comme ici. Sinon ça ne marche pas.

On va ensuite rechercher le script PowerShell et le lancer.

La première fois on récupère ce message:

2021-06-05_22-56-26

(This pipeline needs permission to access a resource before this run can continue to Build stage)

 

Il indique que l’on a besoin de créer un utilisateur associé au service de build afin de lui donner les permissions nécessaires pour exécuter le script.

 

Il faut suivre les instructions et cliquer sur VIEW:

2021-06-05_23-04-27

Puis PERMIT:

2021-06-05_23-22-11

 

Un utilisateur est ajouté avec les bonnes permissions:

2021-06-06_17-18-29

Et:

2021-06-06_17-19-23

Pour en savoir plus:

Check out multiple repositories in your pipeline – Azure Pipelines | Microsoft Docs

NOTE 1:

Si vous galérez sur le checkout, on peut faire un dir pour voir ce qui se passe. Par exemple :

– script: dir $(Build.SourcesDirectory)
NOTE 2:
 
Lors de mon premier essai, checkout et AzurePowershell étaient déployés sur des jobs différents. J’avais un peu oublié que chaque job est lancé dans un tout nouvel agent. Par conséquent la deuxième task ne peut pas récupérer le résultat du checkout et mon script ne marchait pas!
 
 
Il existe diverses façons d’assurer une persistance entre job. Nous allons explorer l’une d’entre elle: les artefacts
 

Les artefacts

Les artefacts sont un outil pour échanger des fichiers entre étapes ou bien entre pipelines. Ils ont typiquement le résultat de sortie d’un processus de build qui sera consommés par un autre job ou un autre pipeline. Les artefacts sont associés au run qui les a généré et y persistent même si celui-ci s’est terminé.
 
Les artefact ne sont en principe pas téléchargés automatiquement, sauf dans le cas des jobs de déploiement. Il est possible de bloquer ce comportement en ajoutant ne étape:
 
steps:
– download: none
Je vois un peu partout mentionné des notions de:
  • artefact de build
  • artefact de pipeline

Je ne suis pas certain de la différence, mais il semble bien que les artefact de build tendent à remplacer les artefacts de pipeline.

Il existe également une notion d’universal package que je ne traiterai pas ici. C’est un artefact, mais il est indépendant du pipeline qui l’a créé.

 

Création du pipeline CI

Ce pipeline a pour fonction de versionner l’orchestrateur, puis de le pousser dans un artefact. Il deviendra alors immutable et pourra servir pour déployer des ressources.

Versionnage de l’orchestrateur

Le pipeline CI va adjoindre au projet un fichier de version contenant le numéro de version. Celui-ci sera incrémenté automatiquement.

Pour marquer le code source correspondant au numéro de version on va également poser un tag Git.

Ce travail est fait en PowerShell via le script Build.ps1:

# build.ps1

Param(
	$repositoryName
)

cd "$($env:Build_SourcesDirectory)/$repositoryName"


$version = @{
	numero = 0
	date = Get-Date
}

$path = "$env:Build_SourcesDirectory" + "/$repositoryName/version"
New-Item -Path $path -ItemType Directory -force

$versionFile = "$path/version.json"
write-host "=> Recherche fichier de version: $versionFile"

If ((Test-Path $versionFile) -eq $True) {
	write-host fichier de version existe déjà
	$version = ConvertFrom-Json $versionFile -asHashtable
}

$version.numero++
write-host "=> Création de la version $($version.numero)"

If(!(test-path $path))
{
      New-Item -ItemType Directory -Force -Path $path
}

ConvertTo-Json $version > $versionFile

Write-Host "=> Add Git tags version $($version.numero)"
git tag $version.numero --force
git push --tags --force

Le script attend en paramètre le nom du repository qui aura auparavant été cloné dans la variable  $(Build.SourcesDirectory). Cette variable peut être récupérée par le script à travers une variable d’environnement. Notez la syntaxe, le point (.) est transformé par un souligné.

On teste ensuite la présence d’un fichier version.json, s’il n’est pas présent on le crée. Ce fichier contient le numéro de version. Dans mon exemple il s’agit d’un entier incrémenté, mais toute autre technique conviendra.

Une fois terminé on tague le repository.

Ce script est lancé par ce Yaml:

name: $(TeamProject)_$(Build.DefinitionName) CI $(Rev:.r)

trigger:
- master

pool:
  vmImage: windows-latest

resources:
  repositories:
    - repository: TutoDeploy
      ref: main
      type: git
      name: Tuto-Deploy

jobs:  
- job: Build

  steps:
    - checkout: self
    - checkout: TutoDeploy
      clean: true

    - task: Powershell@2
      inputs:
        pwsh: true
        filePath: '.\tuto-deploybuild\CI\build.ps1'
        arguments: "-repositoryName tuto-deploy"

Peu de chose à dire, on commence à déclarer une ressource puisque le Yaml se situe dans un autre repository que l’orchestrateur.

On checkout l’orchestrateur ce qui nous oblige aussi à faire de même avec le projet courant car cela ne sera pas fait automatiquement dans ce cas.

Puis on lance build.ps1. Lançons donc!

L’exécution échue avec des messages du style:

fatal: could not read Password for ‘https://oldFmirouze@dev.azure.com/oldAmethyste/Tuto-Pipeline/_git/Tuto-Deploy’: terminal prompts disabled

2021-06-16_22-17-41

Pas cool!

Pour résoudre le problème on doit faire deux choses:

  1. Donner au service de build des permissions
  2. permettre au pipeline d’utiliser les credentials Git

Repérez le menu Project settings:

2021-06-16_22-21-57

Cliquer dessus et repérer le menu Repositories:

2021-06-16_22-24-25

Cliquer dessus et sélectionner l’onglet Security:

2021-06-16_22-25-39

Veiller à ce que les droits Contribute et Create Tag soient bien affectés.

On modifie ensuite le pipeline de la façon suivante:

  steps:
    - checkout: self
    - checkout: TutoDeploy
      clean: true
      persistCredentials: true

PersistCredentials est à true ce qui dépose le jeton OAuth dans la configuration Git une fois le checkout fait. Le script pourra alors accéder au jeton système.

La propriété clean indique de nettoyer le référentiel local.

On peut ensuite relancer et cette fois tout marche:

2021-06-16_22-30-57

On peut vérifier que tout est OK en examinant les tags du projet orchestrateur:

2021-06-16_22-32-27

Cette méthode marche mais il y a un inconvénient. Si je relance et vérifie les tags:

2021-06-16_22-32-27

Aucun n’incrément n’est effectué. La raison, vous l’avez certainement devinée, on ne sauvegarde pas le fichier de version.

Sauvegarde de la version

On va donc compléter build.ps1 qui ressemble maintenant à ceci:

# build.ps1

Param(
	$repositoryName
)

cd "$($env:Build_SourcesDirectory)/$repositoryName"


$version = @{
	numero = 0
	date = Get-Date
}

$path = "$env:Build_SourcesDirectory" + "/$repositoryName/version"
$versionFile = "$path/version.json"
New-Item -Path $path -ItemType Directory -force

write-host "=> Recherche fichier de version: $versionFile"

If ((Test-Path $versionFile) -eq $True) {
	write-host "fichier de version existe déjà"
	$version = Get-Content $versionFile -Encoding UTF8 -Raw | ConvertFrom-Json -AsHashtable
}

$version.numero++
write-host "=> Création de la version $($version.numero)"

If(!(test-path $path))
{
      New-Item -ItemType Directory -Force -Path $path
}

ConvertTo-Json $version > $versionFile

Write-Host "=> Add Git tags version $($version.numero)"
git tag $version.numero --force
git push --tags --force


Write-Host "=> commit nouveau fichier de version: $($version.numero)"

git config --global user.email "you@example.com"
git config --global user.name "Amethyste"

git add * 
git commit -m 'Nouvelle version'

git push origin HEAD:$env:Build_SourceBranchName

Les deux lignes 43,44 sont des exigences Git. On peut renseigner ce que l’on souhaite. La suite est classique: add, commit et push. Remarquez juste comment je récupère le nom de la branche.

Et si on regarde dans le repository Tuto-Deploy:

2021-06-17_22-14-02

Le ficher a bien été créé.

Si je relance une deuxième fois:

2021-06-17_22-27-06

Bref ça marche.

Bien entendu il y a certainement d’autres approches, peut-être moins intrusives. Mais celle-ci est fréquente, laissons lui sa chance.

Création de l’artefact

Nous avons pas terminé. Il nous faut créer l’artefact. On va revenir dans la partie Yaml et la compléter:

name: $(TeamProject)_$(Build.DefinitionName) CI $(Rev:.r)

trigger:
- master

pool:
  vmImage: windows-latest

resources:
  repositories:
    - repository: TutoDeploy
      ref: main
      type: git
      name: Tuto-Deploy

jobs:  
- job: Build
  displayName: Build package

  steps:
    - checkout: self
      clean: true
      displayName: Lire DeployBuild
    - checkout: TutoDeploy
      displayName: Lire orchestrateur
      clean: true
      persistCredentials: true


    - task: Powershell@2
      displayName: 'Versionne package'
      inputs:
        pwsh: true
        filePath: '.\tuto-deploybuild\CI\build.ps1'
        arguments: "-repositoryName tuto-deploy"

    - task: CopyFiles@2
      displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)/amethystePkg'
      inputs:
        SourceFolder: '$(system.defaultworkingdirectory)/Tuto-Deploy'
        TargetFolder: '$(Build.ArtifactStagingDirectory)/amethystePkg'

    - task: ArchiveFiles@2
      displayName: 'Création du package'
      inputs:
        rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/amethystePkg'
        includeRootFolder: false
        archiveType: zip
        replaceExistingArchive: true
        archiveFile: '$(Build.ArtifactStagingDirectory)/amethystePkg.zip'

    - task: PublishPipelineArtifact@0
      displayName: "Publier l'artefact"
      inputs:
        targetPath: '$(Build.ArtifactStagingDirectory)/amethystePkg.zip'

La tâche copyfile copie notre orchestrateur dans $(Build.ArtifactStagingDirectory). C’est le chemin d’accès local sur l’agent où sont copiés les artefacts avant d’être envoyés vers leur destination. Notez que l’on ne récupère que le sous-répertoire Tuto-Deploy. On a en effet toutes les ressources consommées à cet emplacement et on ne veut pas tout copier dans l’artefact.

Ce répertoire est purgé à chaque build, on a donc pas besoin de tâche clean contrairement à checkout qui n’est que partiellement nettoyé.

La tâche archiveFiles créée un zip. C’est notre futur artefact. Il ne deviendra artefact qu’une fois publié avec publishPipelineArtifact. Il existe deux alias: publish et download.

Comment tester que la publication a réellement eu lieu?

Revenons ici:

2021-06-17_23-40-24

Ce panneau est le tableau de bord d’exécution du pipeline.

La partie SOURCE indique les ressources externes qui ont été utilisées. Regardez où se pointe la flèche en haut.

On apprend que l’on a publié 1 artefact. Il s’agit d’un lien sur lequel on peut cliquer, ne soyons pas timide:

2021-06-17_23-44-41

On peut consommer un artefact de diverses façons. Dans l’article qui suit nous le ferons depuis un autre pipeline. Mais on peut aussi le passer à un job, même s’il se trouve dans une autre étape. Ce serai une façon de le tester d’ailleurs.

Il est possible de nommer un artefact ce qui peut être être utile s’ils sont nombreux à l’aide de la propriété artifactName:

- task: PublishPipelineArtifact@0
  displayName: "Publier l'artefact"
  inputs:
	targetPath: '$(Build.ArtifactStagingDirectory)/amethystePkg.zip'
	artifactName: orchestrateur

Et cette fois:

2021-06-21_13-55-43

Bibliographie

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s