Azure Pipelines'ta PowerShell modüllerini önbelleğe alma


Giriş

Bu, işlem hattınız sırasında ihtiyaç duyduğunuz herhangi bir PowerShell modülünü depolamak için Azure DevOps'ta (Azure Pipelines) bir önbelleği nasıl kullanabileceğinizi gösteren kısa bir gönderidir.

İşlem hattınızın PowerShell Galerisi'nin kullanılabilirliğine güvenmesine izin vermeyin!

Bu aslında her şey için geçerli, ancak bu yazıda PowerShell Galerisine odaklanıyorum. Bu açıklamanın iki basit nedeni var:

  1. Galeriden modülleri indirmek zaman alıyor ve boru hattımı her çalıştırdığımda yapmak istediğim bir şey değil.
  2. Galeri kullanılamayabilir, bunun hattımı bozmasını istemiyorum.

Bu nedenlerden dolayı indirilen herhangi bir modülü veya her boru hattı yürütmesi arasındaki diğer bağımlılığı önbelleğe almak istiyorum. Adil olmak gerekirse, tüm bağımlılıkları kendi özel galerimde barındırsaydım muhtemelen daha da iyi olurdum, ancak bu başka bir zamanın yazısı.

Boru hattının hazırlanması

Gelecekte veya projeler veya ortamlar arasında değiştirmek isteyebileceğim herhangi bir yapılandırılabilir değeri ardışık düzen değişkenleri olarak ayarlamayı seviyorum. Bu, hem aynı değeri birden fazla kez yazma ihtiyacını azaltır hem de her değer yalnızca bir kez mevcut olduğunda ve tüm değiştirilebilir değerler aynı yerde tanımlandığında, değerin değiştirilmesini çok daha kolay hale getirir. Çocukların dediği gibi (yoksa öyle mi?) KURU tutun (Kendinizi Tekrarlamayın).

O halde birkaç değişken ayarlayalım. Modülleri saklayacak bir yere ve bunları indiren betiğin yoluna ihtiyacımız olacak.

variables:
  modulesFolder: '$(System.DefaultWorkingDirectory)/powershellmodules'
  restoreModulesScript: '$(Build.Repository.LocalPath)/scripts/restoremodules.ps1'

Bu yapıldıktan sonra modülleri indirmek için komut dosyasını yazmamız gerekiyor. Basit örneğimde örnek olarak ModuleBuilder modülünü kullanacağım. Betiğim, modüllerin kaydedilmesi gereken yere bir Yol parametresi alacak. Daha sonra modülleri PowerShell Gallery'den indirmek için Save-Module kullanacağım.

Burada her modülün tam olarak hangi sürümünü istediğimizi belirtmek önemlidir, çünkü bu betiğin karma toplamını önbelleğe alınmış verilerimizi tanımlayan anahtarın bir parçası olarak kullanacağım. Bu, dosyayı değiştirirsek önbelleğin geçersiz olacağı ve komut dosyasının modülleri tekrar indirmek için çalışacağı anlamına gelir.

Çok sayıda veya karmaşık bağımlılığınız varsa, bunları indiren komut dosyasını yapılandırmadan ayırmanızı öneririm. Bu durumlarda genellikle yapılandırmayı içeren bir dependegency.json veya psd1 dosyasına sahibim ve karma anahtarımı hesaplamak için hem komut dosyasını hem de yapılandırma dosyasını kullanırım. Daha sonra bunun hakkında daha fazla bilgi vereceğiz. Örneğim için kullanacağım komut dosyası:

param(
    $Path
)
if(-not (Test-Path -Path $Path)) {
    $null = New-Item -Path $Path -ItemType Directory
}
Save-Module -Name ModuleBuilder -RequiredVersion '2.0.0' -Path $Path

Önbellek görevini kullanma

Hiç de göründüğü kadar zor değil! Azure DevOps Pipelines'ta klasörleri önbelleğe almaya yönelik yerleşik bir görev vardır! Basitçe "Önbellek" olarak adlandırılır ve Microsoft belgelerinde belgelenmiştir.

Basitçe şu şekilde kullanıyoruz:

- task: Cache@2
  displayName: Cache Powershell Modules
  # This task will restore modules from cache if key is found.
  # If contents of restoremodules.ps1 changes, key changes and cache is not restored.
  # If cache is restored, variable PSModules_IsCached is set to true
  inputs:
    key:  restoremodules | ${{ variables.restoreModulesScript }}
    path: ${{ variables.modulesFolder }}
    cacheHitVar: PSModules_IsCached

Önbellek görevi üç girdi alır: bir anahtar, bir yol ve bir değişken adı. Belgelerdeki tanımlarına bakalım.

Önbellek için anahtar (benzersiz tanımlayıcı)

Bu, '|' kullanılarak bölümlere ayrılabilecek bir dize olmalıdır. Dosya yolları mutlak veya $'ya (System.DefaultWorkingDirectory) göreli olabilir.

Önbelleğe alınacak klasörün yolu

Tamamen nitelenmiş veya $'ya (System.DefaultWorkingDirectory) göreli olabilir. Joker karakterler desteklenmez. Değişkenler desteklenir.

Önbellek isabet değişkeni

Önbellek geri yüklendiğinde (önbellek isabeti) "doğru" olarak ayarlanacak değişken, aksi takdirde "yanlış" olarak ayarlanır.

Burada önbelleğimizin anahtarını ayarladığımızı görebilirsiniz. Dokümantasyonda belirtildiği gibi, pipe-char ile ayrılmış dosyalara yönelik dizeleri veya yolları kullanabiliriz. Bu örnekte restoremodules dizesini betiğimin yoluyla birlikte kullanıyorum. Eğer konfigürasyonumu ayrı bir dosyada tutsaydım o dosyanın yolunu da buraya eklerdim.

Bu görevin çıktısı anahtarın nasıl çözümlendiğini gösterecektir; şöyle görünür:

Bağımlılıkları indirme görevi

Daha sonra restoremodules.ps1 betiğimizi görevde çalıştırmamız gerekiyor. Bu görev yalnızca modüller önbellekten geri yüklenmemişse çalıştırılmalıdır. Bu sorun, PSModules_IsCached ardışık düzen değişkeninin doğru olmamasını gerektiren bir koşul eklenerek çözülür. İşte bunun için yaml:

- task: PowerShell@2
  displayName: 'Download Powershell Modules'
  # This task runs my restoremodules.ps1 script if the cache was not restored.
  condition: ne(variables.PSModules_IsCached, 'true')
  inputs:
    targetType: filePath
    filePath:  ${{ variables.restoreModulesScript }}
    arguments: -Path ${{ variables.modulesFolder }}
    pwsh: true

Bu kısım oldukça açıklayıcıdır. Artık çalışan bir önbellek için ihtiyacımız olan her şeye sahibiz!

Bunun nasıl çalıştığını göstermek için, yalnızca önbellek geri yüklendiğinde çalışacak, şuna benzeyen ekstra bir görev ekleyeceğim:

- task: PowerShell@2
  displayName: 'Log that cached modules were used'
  # This task will only run if modules were restored from cache
  # This task is pointless, just serves to show that cache is working
  condition: eq(variables.PSModules_IsCached, 'true')
  inputs:
    pwsh: true
    targetType: 'inline'
    script: |
      Write-Verbose -Message 'Using cached modules' -Verbose
      Get-ChildItem -Path ${{ variables.modulesFolder }}

İndirilen modüller yükleniyor

Modülleri kendi klasörlerine indirmeyi seçtiğim için PowerShell bunların orada olduğunu bilmeyecek ve bu nedenle bunları otomatik olarak yüklemeyecektir. Bu, modülleri yollarını kullanarak yüklemem veya PowerShell'e bunların nerede bulunabileceğini söylemem gerektiği anlamına gelir. PowerShell'in bunları PSModulePath

ortam değişkenine ekleyerek araması gerektiğini söyleyebilirim. Burada, kalan tüm görevler için yalnızca PSModulePath değişkenini ayarlayan bir göreve sahip olmak istiyorum. boru hattımda, ancak bunu yapmanın iyi bir yolunu bulamadım bu yüzden modüllerin yüklenmesine ihtiyaç duyduğum her görevde PSModulePath'i değiştiriyorum (genellikle zaten o kadar da fazla değil).

İşte nasıl göründüğü:

- task: PowerShell@2
  displayName: 'Load modules'
  # This would be where your actual pipeline starts, I will just load a module to show that it works
  inputs:
    pwsh: true
    targetType: 'inline'
    script: |
      $Env:PSModulePath = '${{ variables.modulesFolder }}', $Env:PSModulePath -join [System.IO.Path]::PathSeparator
      Write-Host $Env:PSModulePath
      Import-Module "ModuleBuilder"
      Get-Module ModuleBuilder | Format-List -Property *

Özet

Özetlemek gerekirse, PowerShell modüllerini (veya gerçekten herhangi bir şeyi) önbelleğe almak istiyorsak, önbelleğimizdeki içeriği arayan bir önbellek görevi ekleyebiliriz. Önbellek görevi, belirttiğimiz klasörü önbelleğe yükleyeceği ardışık düzenimize otomatik olarak bir görev sonrası ekleyecektir.

Boru hattım ilk kez çalıştırıldığında şöyle görünecek:

PowerShell Modüllerini İndir görevinin çalıştırıldığını ve klasörü önbelleğime yükleyen bir iş sonrası olduğunu görebiliriz.

Aynı boru hattını tekrar çalıştırırsam şöyle görünecektir:

Burada PowerShell Modüllerini İndirme görevi atlandı ve tam 11 saniye tasarruf ettik! Bu örnekte gerçekten küçük bir modül olduğu için çok fazla zaman tasarrufu sağlamıyoruz, ancak işlem hattımızın mevcut PowerShell Galerisine bağımlı olmadan çalışacağını da biliyoruz.

Tüm boru hattı yaml'ım şöyle görünüyor:

trigger:
- main

pool:
  vmImage: ubuntu-latest

variables:
  modulesFolder: '$(System.DefaultWorkingDirectory)/powershellmodules'
  restoreModulesScript: '$(Build.Repository.LocalPath)/scripts/restoremodules.ps1'

steps:

- task: Cache@2
  displayName: Cache Powershell Modules
  # This task will restore modules from cache if key is found.
  # If contents of restoremodules.ps1 changes, key changes and cache is not restored.
  # If cache is restored, variable PSModules_IsCached is set to true
  inputs:
    key:  restoremodules | ${{ variables.restoreModulesScript }}
    path: ${{ variables.modulesFolder }}
    cacheHitVar: PSModules_IsCached
  

- task: PowerShell@2
  displayName: 'Download Powershell Modules'
  # This task runs my restoremodules.ps1 script if the cache was not restored.
  condition: ne(variables.PSModules_IsCached, 'true')
  inputs:
    targetType: filePath
    filePath:  ${{ variables.restoreModulesScript }}
    arguments: -Path ${{ variables.modulesFolder }}
    pwsh: true

- task: PowerShell@2
  displayName: 'Log that cached modules were used'
  # This task will only run if modules were restored from cache
  # This task is pointless, just serves to show that cache is working
  condition: eq(variables.PSModules_IsCached, 'true')
  inputs:
    pwsh: true
    targetType: 'inline'
    script: |
      Write-Verbose -Message 'Using cached modules' -Verbose
      Get-ChildItem -Path ${{ variables.modulesFolder }}

- task: PowerShell@2
  displayName: 'Load modules'
  # This would be where your actual pipeline starts, I will just load a module to show that it works
  inputs:
    pwsh: true
    targetType: 'inline'
    script: |
      $Env:PSModulePath = '${{ variables.modulesFolder }}', $Env:PSModulePath -join [System.IO.Path]::PathSeparator
      Write-Host $Env:PSModulePath
      Import-Module "ModuleBuilder"
      Get-Module ModuleBuilder | Format-List -Property *

Önbelleği nasıl kullandığınızı bana bildirin! Aşağıya bir yorum bırakın.

İlgili makaleler