GitLab CI/CD mit PowerShell – Teil 2 – Continuous Integration

In Teil 1 der Serie GitLab CI/CD mit PowerShell haben ich meinen Aufbau und den Installationsvorgang der GitLab Umgebung für kleine PowerShell Projekte aufgezeigt.
Nun steigen wir mit diesem Artikel direkt ein, was ein Projekt für Continuous Integration zusätzlich benötigt und wie die Tests bzw. ein Deployment aussehen können.

GitLab CI/CD mit PowerShell – Einführung

Continuous Integration ist In GitLab 8 voll integriert und bei jedem Projekt standardmäßig aktiviert. Um eine CI Pipline bei jedem Merge oder Push auszulösen muss lediglich ein weiteres File im Root des Projektes angelegt werden.

Zusätzliches File: .gitlab-ci.yml

git add .gitlab-ci.yml
git commit -m "Add .gitlab-ci.yml"
git push origin master

In diesem File werden alle Schritte für den Runner definiert. Die Schritte sind in Stages aufgeteilt: Build, Test, Deploy

Außerhalb der Scripts in den Stages können in dem YAML File noch Pre- und Post-Scripte sowie Variablen definiert werden.
Die GitLab Doku bietet hier einen sehr guten Einblick in die umfangreichen Möglichkeiten.

GitLab CI/CD mit PowerShell – Tests

Wir schauen uns in dem Folgenden Beispiel als erstes die wohl am häufig genutzte Stage an, Test.

.gitlab-ci.yml Beispiel für Tests

variables:
  GIT_SSL_NO_VERIFY: "true"

before_script:
  - Import-Module PSScriptAnalyzer
  - Import-Module Pester
  
stages:
  - test

ps_scriptanalyzer:
  stage: test
  script: 
  - $res = (Invoke-ScriptAnalyzer -Path . -Severity Error).count
  - if ($res -gt 0) { throw "$($res) Analytics failed."}
  tags:
  - Windows

ps_pester:
  stage: test
  script: 
  - cd Tests
  - $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile TestsResults.xml -PassThru
  - if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed."}
  tags:
  - Windows

Was haben wir in dem Beispiel definiert:

  1. Variable: Deaktivierung der SSL Verifizierung wegen eines sporadischen Problems
  2. Pre-Script: Import der PowerShell Module Pester und PSScriptAnalyzer
  3. Stages: Nur Test Stage
  4. Job Name: PS_ScriptAnalyzer
    1. Script: Ausführung der PSScriptAnalyzer Analysten mit Prüfung auf Error Level Fehler
    2. Tags:  Windows (zur Auswahl des Runners)
  5. Job Name: PS_Pester
    1. Script: Ausführung der Pester Tests mit Prüfung der Fehler
    2. Tags:  Windows (zur Auswahl des Runners)

Der erfolgreiche Lauf der Pipeline sieht dann so aus

GitLab CI/CD mit PowerShell - Pipeline

 

PSScriptAnalyzer Log

ps_scriptanalyzer-log

Pester Log

ps_pester-log_v2

Die beiden Beispiel Tests stellen auch für mich meine Baseline der Qualitätskontrolle in meinen Projekten dar.

  • Qualität des Codes mit PSScriptAnalyzer anhand der Regeln Prüfen
  • Grundlegende Tests mit Pester festlegen

Durch dieses Vorgehen ist die Prüfung des Projekts auch jederzeit außerhalb von GitLab CI/CD mit PowerShell möglich und muss nicht separat erarbeitet werden.

PSScriptAnalyzer

Was ist PSScriptAnalyzer

PSScriptAnalyzer is a static code checker for Windows PowerShell modules and scripts. PSScriptAnalyzer checks the quality of Windows PowerShell code by running a set of rules. The rules are based on PowerShell best practices identified by PowerShell Team and the community. It generates DiagnosticResults (errors and warnings) to inform users about potential code defects and suggests possible solutions for improvements.

In meine Tests in GitLab ziehe ich nur die Errors in PSScriptAnalyzer zu rate. Dies sind z.B. nicht verwendete Variablen oder die Verwendung von reservierten Parametern in Funktionen.
Alle Regeln mit deren Level können in der Übersicht der PSScriptAnalyzer Regel Dokumentation auf GitHub eingesehen werden.

Installation von PSScriptAnalyzer

PowerShell 5

Mit PowerShell 5 ist der beste Weg die direkte Installation aus der PowerShell Gallery.

Install-Module -Name PSScriptAnalyzer
Import-Module -Name PSScriptAnalyzer

PowerShell 4

Der einfachste Weg ist der Umweg über die PowerShell 5.

  • Das Modul in PowerShell 5 herunterladen
Save-Module -Name PSScriptAnalyzer -Path C:\Temp\
  • Dann den Ordner in „C:\Windows\System32\WindowsPowerShell\v1.0\Modules“ auf dem Server mit PowerShell 4 kopieren
  • Modul laden
Import-Module -Name PSScriptAnalyzer

Pester

Was ist Pester

Pester provides a framework for running unit tests to execute and validate PowerShell commands from within PowerShell. Pester consists of a simple set of functions that expose a testing domain-specific language (DSL) for isolating, running, evaluating and reporting the results of PowerShell commands.

Pester tests can execute any command or script that is accessible to a Pester test file. This can include functions, cmdlets, modules and scripts. Pester can be run in ad-hoc style in a console or it can be integrated into the build scripts of a continuous integration (CI) system.

Pester ist nicht erst seit GitLab CI/CD mit PowerShell ein wichtiges Werkzeug für mich. Daher versuche ich die CI Tests innerhalb von GitLab so zu erstellen, dass die Tests auch weiterhin direkt in Pester brauchbar sind.

Für Pester Neulinge ist das GitHub Wiki ein guter Start in das Thema.

Installation von Pester

PowerShell 5

Mit PowerShell 5 ist der beste Weg wieder die direkte Installation aus der PowerShell Gallery.

Install-Module -Name Pester
Import-Module -Name Pester

PowerShell 4

Der einfachste Weg ist der Download des GitHub Pester Repositorys.

  • Das Repository als Zip herunterladen
  • Dann das Zip in „C:\Windows\System32\WindowsPowerShell\v1.0\Modules“ auf dem Server mit PowerShell 4 extrahieren
  • Modul laden
Import-Module -Name Pester

Meine Basis Tests mit Pester

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"


Describe "Get-VMID" {
    It "Import Module" {
        $Error.Clear()
        Import-Module ../Get-VMID.psm1
        $PetserTest = (Get-Module -Name Get-VMID)
        $PetserTest.Count | Should BeGreaterThan 0
        $Error.Count | Should BeLessThan 1
    }
    It "Run Module" {
        $Error.Clear()
        $Trash = Add-PSSnapin -PassThru VMware.VimAutomation.Core -ErrorAction SilentlyContinue  -WarningAction SilentlyContinue
        $Trash = Connect-VIServer -Server "myvcenter.lan.local" -Force -ErrorAction SilentlyContinue  -WarningAction SilentlyContinue
        $PetserTest = (Get-VM -Name TEST* | Get-VMID)
        $PetserTest.Count | Should BeGreaterThan 0
        $Error.Count | Should BeLessThan 1
    }
}

Was Teste ich an Funktionen immer mindestens mit Pester:

  • Fehlerfreier Import der Funktion
  • Grundlegende Nutzung der Funktion (Wenn das non-Destructive möglich ist)

GitLab CI/CD mit PowerShell – Deploy

Fügt man dem YAML File noch die Stage Deploy hinzu und definiert diese, werden alle Deploy Job Scripte nach den erfolgreichen Tests ausgeführt.

.gitlab-ci.yml Beispiel mit Deployment

variables:
  GIT_SSL_NO_VERIFY: "true"

before_script:
  - Import-Module PSScriptAnalyzer
  - Import-Module Pester

stages:
  - test
  - deploy

ps_scriptanalyzer:
  stage: test
  script: 
  - $res = (Invoke-ScriptAnalyzer -Path . -Severity Error).count
  - if ($res -gt 0) { throw "$($res) Analytics failed."}
  tags:
  - Windows

ps_pester:
  stage: test
  script: 
  - cd Tests
  - $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile TestsResults.xml -PassThru
  - if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed."}
  tags:
  - Windows

copy_to_webserver:
  stage: deploy
  script:
  - $Error.Clear()
  - cd $CI_PROJECT_DIR
  - Copy-Item *.ps1 -Destination \\myServer\Folder
  - Copy-Item *.xml -Destination \\myServer\Folder
  - if ($Error -gt 0) { throw "$($Error) Errors occured."}
  tags:
  - Windows

add_to_zip:
  stage: deploy
  script:
  - $Error.Clear()
  - cd $CI_PROJECT_DIR
  - $FilePath = $CI_PROJECT_DIR + '\myFile.ps1'
  - Add-Type -As System.IO.Compression.FileSystem
  - $Archive = [System.IO.Compression.ZipFile]::Open('\\myServer\Folder\myZip.zip','Update')
  - $Entry = $Archive.GetEntry('myZip.ps1')
  - if ($Entry) {$Entry.Delete()}
  - $Trash = [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($Archive, $FilePath, 'myFile.ps1')
  - $Archive.Dispose()
  - if ($Error -gt 0) { throw "$($Error) Errors occured."}
  tags:
  - Windows

In den Deploy Jobs habe ich auf eine GitLab Runner Variable zugegriffen: $CI_PROJECT_DIR

Die Variable beinhaltet den Pfad in welches das Repositoy für den Build kopiert wird.

Der erfolgreiche Lauf der kompletten Pipeline sieht dann so aus

gitlab-pipeline_deploy

2 Comments

  1. Andreas Frei 11. Januar 2022
    • Markus Kraus 11. Januar 2022

Leave a Reply