OpsaC - Operating as PowerShell code
published: October 28, 2023 author: Tinu tags: PowerShell categories: GitLab
GitLab Pipeline is your friend - UNDER CONSTRUCTION!

A GitLab CI/CD pipeline is the file .gitlab-ci.yml in the root of your project.
if: $CI_PIPELINE_SOURCE == 'merge_request_event'
if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
if: $CI_COMMIT_TITLE =~ /TEST:.*/
List of stages for jobs, and their order of execution. If stages is not defined in the .gitlab-ci.yml file, the default pipeline stages are:
.pre
build
test
deploy
.post
Hidden Jobs begins with a point in the name .cleanup_git.
.cleanup_git:
  stage: .post
  script:
    - echo "Running Job $CI_JOB_NAME, $CI_COMMIT_BRANCH!"
Normal Job:
test-node-yaml-test:
  rules:
    - if: $CI_COMMIT_TITLE =~ /TEST:.*/
  tags: [auto-deploy-test]
  extends: .test_node_yaml
Tags of your GitLab-Runner.
Reuse the configuration or Scripts.
extends: .test_node_yaml # included script
extends: .deploy_script  # hidden job
Configurations and Scripts outside of the .gitlab-ci.yml.
include: 
  - local: '/CI/init-env.yml'
  - local: '/CI/build-job.yml'
  - local: '/CI/test-job.yml'
  - local: '/CI/deploy-job.yml'
  - local: '/CI/cleanup-runner.yml'
A Script can be either a single Command or a Scriptblock.
Single Command:
script:
  - echo "Running Job $CI_JOB_NAME, $CI_COMMIT_BRANCH!"
Scriptblock:
script: |
  Write-Host "Start Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
  if(-not(Test-Path D:\Pikett-Scripts)){
    New-Item D:\Pikett-Scripts -ItemType Directory -Force | Select LastWriteTime, Length, Name, Fullname
  }
  Copy-Item "*.ps1" "D:\Pikett-Scripts" -PassThru -Force | Select LastWriteTime, Length, Name, Fullname
  if($LastExitCode -gt 0) { Throw "LastExitCode $($LastExitCode)" }
  Write-Host "End Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
[ Top ]
A very simple Pipeline.
# A pipeline is composed of independent jobs that run scripts, grouped into stages.
# Stages run in sequential order, but jobs within stages run in parallel.
# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
workflow:
  name: 'Pipeline for do anything for me'
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH  
stages:
  - test
  - deploy
  - cleanup
.execute_script:
  before_script:
    - Write-Host "Running Job '$CI_JOB_NAME' on Branch '$CI_COMMIT_BRANCH' with commit message '$CI_COMMIT_TITLE'!"
test-job:
  stage: test
  tags: [test-win-adm]
  extends: .execute_script
  script:
    - Write-Host "Doing some test"
deploy-job:
  stage: deploy
  tags: [test-win-adm]
  extends: .execute_script
  script: |
    Write-Host "Doing some deployment to:"
    $ProjectFullname = Get-Item $CI_PROJECT_DIR
    tree /A ($ProjectFullname.FullName).ToLower()
cleanup-job:
  stage: cleanup
  tags: [test-win-adm]
  extends: .execute_script
  script: |
    Write-Host "Doing some cleanup on: $CI_BUILDS_DIR"
    $SplittedPath = $CI_BUILDS_DIR -split '\\'
    $RootPath = Join-Path -Path $SplittedPath[0] -ChildPath $SplittedPath[1]
    Write-Host "Set the location to $RootPath"
    Set-Location $RootPath
    $CleaningPath = Join-Path -Path $RootPath -ChildPath $SplittedPath[2]
    Write-Host "Cleaning up the $CleaningPath"
    Remove-Item $CleaningPath -Recurse -Force
[ Top ]
Copy items to the Runner in a specified target path.
workflow:
  name: 'Pipeline to deploy any Pikett-Scripts to the Script-Host'
stages:
  - build
  - test
.deploy_script: 
    stage: build
    only:
      - master
    script: |
      Write-Host "Start Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
      Write-Host "Currently do only git pull"
      if(-not(Test-Path D:\Pikett-Scripts)){
        New-Item D:\Pikett-Scripts -ItemType Directory -Force | Select LastWriteTime, Length, Name, Fullname
      }
      Copy-Item "*.ps1" "D:\Pikett-Scripts" -PassThru -Force | Select LastWriteTime, Length, Name, Fullname
      if($LastExitCode -gt 0) { Throw "LastExitCode $($LastExitCode)" }
      Write-Host "End Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
deploy-cloud:  # This job runs in the build stage, which runs first of defined stages.
  tags: [pikett-cloud]
  extends: .deploy_script
[ Top ]
This Pipeline does only execute if the commit message starts with TEST:.
Execute Pester-Tests, upload the report as artifact back to the Git-Repository.
# A pipeline is composed of independent jobs that run scripts, grouped into stages.
# Stages run in sequential order, but jobs within stages run in parallel.
# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
      when: never
stages: # List of stages for jobs, and their order of execution
  - build
  - test
  - deploy
include: 
  - local: '/CI/test-node.yml'
  - local: '/CI/deploy-job.yml'
  - local: '/CI/cleanup-runner.yml'
# Run stage deploy and cleanup for commit TEST only the test runner
test-node-yaml-test:
  rules:
    - if: $CI_COMMIT_TITLE =~ /TEST:.*/
  tags: [auto-deploy-test]
  extends: .test_node_yaml
deploy-job-test:
  rules:
    - if: $CI_COMMIT_TITLE =~ /TEST:.*/
  tags: [auto-deploy-test]
  extends: .deploy_job
cleanup-runner-test:
  rules:
    - if: $CI_COMMIT_TITLE =~ /TEST:.*/
  tags: [auto-deploy-test]
  extends: .cleanup_runner
[ Top ]
.test_node_yaml:  # This job runs in the test stage, which runs second of defined stages.
  stage: test
  script: |
    Write-Host "Start Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
    Write-Host "Initialize Modules"
    Get-Module -Listavailable psyml, pester | Select Name,Version | Out-String
    Write-Host "Import Modules"
    Import-Module -Name psyml
    Import-Module -Name Pester -MinimumVersion 5.3.3
    Write-Host "Initialize path-variable"
    $ParentLocation  = Get-Location | Select -ExpandProperty Path
    $ReportsPath     = Join-Path -Path $ParentLocation -ChildPath 'Reports'
    $TestsPath       = Join-Path -Path $ParentLocation -ChildPath 'Tests'
    $BinPath         = Join-Path -Path $ParentLocation -ChildPath 'Bin'
    $ReadmeFullName  = Join-Path -Path $ParentLocation -ChildPath 'README.md'
    $NReportFullName = Join-Path -Path $ReportsPath -ChildPath 'Last-TestsReport.NUnitXml'
    $JReportFullName = Join-Path -Path $ReportsPath -ChildPath 'Last-TestsReport.JUnit.Xml'
    Write-Host "Run Pester Tests"
    $config = [PesterConfiguration]::Default
    $config.Run.Path           = $TestsPath
    $config.Filter.Tag         = "Required"
    $config.Output.Verbosity   = 'Normal'
    $config.Run.PassThru       = $true
    $config.Run.Throw          = $true
    $NUnitReport = Invoke-Pester -Configuration $config
    Write-Host "Create Last-TestsReport.NUnitXml"
    if(-not(Test-Path $ReportsPath)){
      $null = New-Item -Path $ReportsPath -ItemType Directory
    }
    $NUnitReport | Export-NUnitReport -Path $NReportFullName
    $NUnitReport | Export-JUnitReport -Path $JReportFullName
    Get-ChildItem $ReportsPath | Select LastWriteTime, Length, Name, Fullname | Format-List
    if($LastExitCode -gt 0) { Throw "LastExitCode $($LastExitCode)" }
    Write-Host "End Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
  artifacts:
    when: always
    paths:
      - Reports/Last-TestsReport.JUnit.Xml
    reports:
      junit: Reports/Last-TestsReport.JUnit.Xml
    expire_in: 2 days
[ Top ]
.deploy_job:  # This job runs in the deploy stage, which runs third of defined stages.
  stage: deploy
  script: | 
    Write-Host "Start Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
    $ParentLocation   = Get-Location | Select -ExpandProperty Path
    $NodeFolder       = Join-Path -Path $ParentLocation -ChildPath 'Nodes'
    $TemplateFolder   = Join-Path -Path $ParentLocation -ChildPath 'Templates'
    $AutoDeployFolder = Join-Path -Path $TemplateFolder -ChildPath 'AutoDeploy'
    $BinPath          = Join-Path -Path $ParentLocation -ChildPath 'Bin'
    $yamlpath         = Join-Path -Path $NodeFolder -ChildPath '*.yml'
    $yamlfile         = Get-ChildItem $yamlpath
    foreach($item in $yamlfile){
      $node = Get-Content -Path $item.FullName | ConvertFrom-Yaml
      if($node.Status -match 'new'){
        Write-Host "New node $($item.Name)"
        switch($node.DeployType){
          'AutoDeploy' {
            Write-Host "DeployType $($node.DeployType)"
            Write-Host $node | Out-String | Format-List
            #$NodeCsv = "$($node.ESXiHost.ToUpper()).csv"
            if(-not(Test-Path $AutoDeployFolder)){
              New-Item -Path $AutoDeployFolder -ItemType Directory -Force | Select-Object LastWriteTime, Name, Fullname | Format-List
            }
            #$node | ConvertTo-Csv -NoTypeInformation | Out-File -FilePath $(Join-Path -Path $AutoDeployFolder -ChildPath $NodeCsv) -Encoding utf8 -Force
            $NodeYml = "$($node.ESXiHost.ToLower()).yml"
            $node | ConvertTo-Yaml -AsArray | Out-File -FilePath $(Join-Path -Path $AutoDeployFolder -ChildPath $NodeYml) -Encoding utf8 -Force
            Copy-Item $(Join-Path -Path $BinPath -ChildPath 'Execute-Workflow.ps1') -Destination $AutoDeployFolder -Force -PassThru | Select-Object LastWriteTime, Length, Name, Fullname | Format-List
            Copy-Item $(Join-Path -Path $BinPath -ChildPath 'Set-BmcSettings.ps1') -Destination $AutoDeployFolder -Force -PassThru | Select-Object LastWriteTime, Length, Name, Fullname | Format-List
            Copy-Item $(Join-Path -Path $BinPath -ChildPath 'Prep-CisHardening.ps1') -Destination $AutoDeployFolder -Force -PassThru | Select-Object LastWriteTime, Length, Name, Fullname | Format-List
          }
        }
      }
      if($node.Status -match 'done'){
        Write-Host "Node already done $($item.Name)"
      }
    }
    Write-Host "End Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)" 
  artifacts:
    when: always
    paths:
      - Templates/AutoDeploy/*.yml
      - Templates/AutoDeploy/*.ps1
    expire_in: 1 days
[ Top ]
.cleanup_runner: # This job runs in the .post stage, which runs last.
  stage: .post
  script: |
    Write-Host "Start Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
    $array = $CI_PROJECT_DIR -split '\\'
    $RootPath = $(Join-Path -Path $array[0] -ChildPath $array[1])
    $Target = Join-Path $RootPath -ChildPath 'Templates'
    Write-Host "RootPath $($RootPath)"
    Write-Host "Target $($Target)"
    
    if(Test-Path $Target){
      Get-ChildItem $Target
      Remove-Item -Path $Target -Recurse -Force -Confirm:$false
    }
    Write-Host "Do copy"
    Copy-Item -Path $(Join-Path -Path $CI_PROJECT_DIR -ChildPath Templates) -Destination $RootPath -Recurse -Force -Whatif
    Copy-Item -Path $(Join-Path -Path $CI_PROJECT_DIR -ChildPath Templates) -Destination $RootPath -Recurse -Exclude '.gitkeep' -Force -Confirm:$false -PassThru
    
    Write-Host "After copy"
    Get-ChildItem $Target
    
    Write-Host "Do cleanup"
    $CleanupPath = $(Join-Path -Path $array[0] -ChildPath $(Join-Path -Path $array[1] -ChildPath $array[2]))
    Set-Location $RootPath
    Get-Location | Select -ExpandProperty Path | Out-String
    if(Test-Path $CleanupPath){
      Remove-Item -Path $CleanupPath -recurse -force -Confirm:$false
    }
    if($LastExitCode -gt 0) { Throw "LastExitCode $($LastExitCode)" }
    Write-Host "End Job-ID $($CI_JOB_ID), Job-Name $($CI_JOB_NAME)"
[ Top ]
BeforeDiscovery {
    $RootFolder = $PSScriptRoot | Split-Path -Parent
    $NodeFolder = Join-Path -Path $RootFolder -ChildPath 'Nodes'
    $yamlpath   = Join-Path -Path $NodeFolder -ChildPath '*.yml'
    $yamlfile   = Get-ChildItem $yamlpath
}
Describe "Validate Input from <_.Name>" -Tag 'Required' -ForEach $yamlfile {
    BeforeAll{
        $yamlobject = get-content $_.FullName | ConvertFrom-Yaml
    }
    It "[POS] The Property ESXiHost should contains characters and numbers" {
        $($yamlobject.ESXiHost) | Should -Match "\w+\d+"
    }
    It "[POS] The Property IPv4Address should contains IPv4Address" {
        $($yamlobject.IPv4Address) | Should -Match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
    }
    It "[POS] The Property SubnetMask should contains Subnet mask" {
        $($yamlobject.SubnetMask) | Should -Match '^(((255\.){3}(255|254|252|248|240|224|192|128+))|((255\.){2}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){2})|((255|254|252|248|240|224|192|128|0+)(\.0+){3}))$'
    }
    It "[POS] The Property DefaultGateway should contains IPv4Address" {
        $($yamlobject.DefaultGateway) | Should -Match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
    }
    It "[POS] The Property PrimaryDNSServer should contains IPv4Address" {
        $($yamlobject.PrimaryDNSServer) | Should -Match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
    }
    It "[POS] The Property SecondaryDNSServer should contains IPv4Address" {
        $($yamlobject.SecondaryDNSServer) | Should -Match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
    }
    It "[POS] The Property Status should contains done or new" {
        $($yamlobject.Status) | Should -Match 'done|new'
    }
    It "[POS] The Property DeployType should contains Manual, AutoDeploy or Kickstart" {
        $($yamlobject.DeployType) | Should -Match 'Manual|AutoDeploy|Kickstart'
    }
    It "[POS] The Property InstallType should contains cleanup or new" {
        $($yamlobject.InstallType) | Should -Match 'cleanup|new'
    }
    It "[POS] The Property vCenter should exists" {
        $($yamlobject.vCenter) | Should -Match '\w+\d+'
    }
}
[ Top ]
Describe "Test for duplicated values" -Tag 'Required' {
    BeforeAll {
        $RootFolder = $PSScriptRoot | Split-Path -Parent
        $NodeFolder = Join-Path -Path $RootFolder -ChildPath 'Nodes'
        function Get-DuplicatedValue {
            [CmdletBinding()]
            param(
                [Parameter(
                    Mandatory=$true,
                    ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true,
                    Position = 0
                )]
                [String]$Filed
            )
    
            # Duplicated fields
            #Import-Module "$($env:ProgramFiles)\PowerShell\Modules\psyml"
            $Nodes = foreach($node in (Get-ChildItem $NodeFolder )){
                Get-Content -Path $node.FullName | ConvertFrom-Yaml
            }
            $ret = foreach($item in ($Nodes.$Filed | Group-Object)){
                if($item.count -gt 1){
                    $item.Name
                }
            }
            return $ret
        }
    }
    It "[POS] <_> should not have duplicated values" -ForEach 'ESXiHost', 'IPv4Address', 'vMotionIPv4Address', 'MacAddress', 'BmcIPaddress', 'BmcMacAddress' {
        Get-DuplicatedValue -Filed $_ | Should -BeNullOrEmpty
    }
}
[ Top ]



Ensure that you do not have any try-catch in your PowerShell-code.
CI/CD pipelines on GitLab docs