Jenkins持續部署-建立差量更新包

傑哥很忙發表於2019-07-27

Jenkins持續部署-建立差量更新包


目錄

Jenkins持續整合學習-Windows環境進行.Net開發1
Jenkins持續整合學習-Windows環境進行.Net開發2
Jenkins持續整合學習-Windows環境進行.Net開發3
Jenkins持續整合學習-Windows環境進行.Net開發4
Jenkins持續整合學習-搭建jenkins問題彙總
Jenkins持續部署-Windows環境持續部署探究1
Jenkins持續部署-自動生成版本號
Jenkins持續部署-建立差量更新包

前言

上一篇文章介紹關於版本號的自動生成邏輯,本篇文章主要介紹通過指令碼跟版本號建立程式的差量包。

目的

本章主要是通過jenkins持續整合之後通過powershell生成差量更新包與全量更新包,然後將他們上傳到FTP上。

詳細流程

當jenkins編譯完成之後,我們需要處理以下事項。

20190727151702.png

  1. jenkins編譯成功,先獲取所有exe,dll,獲取他們的版本號,建立檔案更新清單。
  2. 將所有的exe,dll,pdb以及檔案更新清單進行壓縮打包,壓縮檔名為指定主程式的版本號。
  3. 將上個版本的檔案壓縮包解壓,若沒有上個版本的檔案壓縮包,則無需上傳差量更新包。
  4. 比較當前檔案更新清單和上個版本的檔案更新清單。若檔名一樣,版本號也一樣,則刪除編譯出的對應檔案。
  5. 獲取剩下的所有exe,dll,pdb以及檔案更新清單進行壓縮打包,壓縮檔名為指定主程式的版本號_Diff。
  6. 遍歷每個服務配置。
  7. 獲取上傳的相對目錄為Job名/版本號.zip,呼叫遠端命令,檢查伺服器是否存在目錄,沒有的話則建立目錄。
  8. 目錄建立完畢後,將壓縮檔案上傳,若有差量更新包則也需要上傳。
  9. 最後呼叫遠端命令對服務進行解除安裝與更新。

生成版本號

上一章介紹了.net環境下如何自動生成版本號的邏輯,這裡不再介紹。有興趣的可以看《Jenkins持續部署-自動生成版本號

獲取版本號

$version =(Get-ChildItem $executeFile).VersionInfo.FileVersion
  1. $executeFile為可執行的exe檔案。通過Get-ChildItem 獲取到該檔案。
  2. 通過VersionInfo.FileVersionVersionInfo.ProductVersion獲取到檔案的檔案版本號。

    不知道為什麼和檔案右鍵屬性看到的兩個版本號有點區別右擊屬性的檔案版本號和產品版本可能不一樣,但是通過程式碼獲取到的都是一樣的

建立檔案更新清單

為了能夠生成程式差量更新包,減少程式更新時的更新包大小,需要可以通過比較當前版本和上一個版本的各個檔案的版本號資訊。通過將該檔案儲存成一份檔案清單。再比較時直接通過該檔案清單進行比對,能夠很方便的生成從差量的檔案清單。同時檔案清單能夠清晰的列出每個需要更新的檔案和檔案版本號,給人看會比較直觀。

主要是我們自己需要用該檔案,也可以不用該檔案,直接獲取所有檔案,對比他們的檔名和版本號是否一致也可以。


$programFiles = Get-ChildItem *.dll,*.exe
$updateListFileName = "FileUpdateList.txt"
Create-FileUpdateList -files $programFiles -fileName $updateListFileName

function Create-FileUpdateList(){
param([System.IO.FileInfo[]]$files,[string]$fileName)

    ## 刪除原始的清單檔案
    if(Test-Path $fileName)
    {
        Write-Host "Remove Old UpdateList File"
        Remove-Item $fileName
    }

    $array=New-Object System.Collections.ArrayList
    foreach($file in $files)   
    {
        ## 獲取每個檔案版本號
        $fileVersion =(Get-ChildItem $file).VersionInfo.ProductVersion
        $fileInfo="" | Select-Object -Property FileName,Version
        $fileInfo.FileName =  $file.Name
        $fileInfo.Version =  $fileVersion
        ## 追加到檔案
        $null = $array.Add($fileInfo)
        Write-Host "Update File:"$file.Name ",Version:" $fileVersion
    }
    $json = ConvertTo-Json $array.ToArray() 
    $json >> $fileName
}
  1. 通過Get-ChildItem *.dll,*.exe獲取當前目錄所有的dll和exe檔案
  2. 將更新清單檔名設定為FileUpdateList.txt,若已經存在更新清單檔案,則刪除舊的重新生成。
  3. 通過New-Object建立一個.Net的集合,用於存放每個檔案的檔案資訊。
  4. $fileInfo="" | Select-Object -Property FileName,Version定義了一個自定義物件型別,包含了檔名和版本號2個屬性。
  5. 將每個檔案的檔名和版本號儲存到集合中
  6. 將集合轉化為Json格式儲存到檔案中.

由於我需要在powershell2.0的環境上執行指令碼,powershell2.0沒有Json讀寫的api,這裡使用的是別人寫的一個指令碼生成json格式的字串。

function  ConvertTo-Json
{
    param(
    $InputObject
    )
    if( $InputObject -is [string]){
            "`"{0}`"" -f $InputObject
 
    }
    elseif( $InputObject -is [bool])
    {
        $InputObject.ToString().ToLower()
    }
    elseif( $null -eq $InputObject){
       "null"
    }
    elseif( $InputObject -is [pscustomobject])
    {
        $result = "$space{`r`n"
        $properties =  $InputObject | Get-Member -MemberType NoteProperty |
        ForEach-Object {
            "`"{0}`": {1}" -f  $_.Name, (ConvertTo-Json $InputObject.($_.Name))
        }
         
        $result += $properties -join ",`r`n"
        $result += "$space`r`n}"
        $result
    }
    elseif( $InputObject -is [hashtable])
    {
        $result = "{`r`n"
        $properties =  $InputObject.Keys |
        ForEach-Object {
            "`"{0}`": {1}" -f  $_, (ConvertTo-Json $InputObject[$_])
        }
         
        $result += $properties -join ",`r`n"
        $result += "`r`n}"
        $result
    }
    elseif( $InputObject -is [array])
    {
        $result = "[`r`n"
        $items = @()
        for ($i=0;$i -lt $InputObject.length;$i++)
        {
          $items += ConvertTo-Json $InputObject[$i]
        }
        $result += $items -join ",`r`n"
        $result += "`r`n]"
        $result
    }
    else{
       $InputObject.ToString()
    }
}

壓縮

將所有檔案進行壓縮,壓縮檔名為版本號。


function New-ZipFile()
{
    param(## The name of the zip archive to create  
        [object[]]$files,
        $zipName = $(throw "Specify a zip file name"),
        ## Switch to delete the zip archive if it already exists.    
        [Switch] $Force)
    ## Check if the file exists already. If it does, check 
    ## for -Force - generate an error if not specified. 
    if(Test-Path $zipName) 
    {    
        if($Force)    
        {        
            Write-Host "Remove File:" $zipName
            Remove-Item $zipName
        }
        else    
        {
            throw "Item with specified name $zipName already exists."    
        }
    }
    ## Add the DLL that helps with file compression 
    Add-Type -Assembly System
    try 
    {    
        #開啟或建立檔案流
        $compressedFileStream = [System.IO.File]::Open($zipName,[System.IO.FileMode]::OpenOrCreate)

        #建立壓縮檔案流
        $compressionStream = New-Object ICSharpCode.SharpZipLib.Zip.ZipOutputStream($compressedFileStream)
        ## Go through each file in the input, adding it to the Zip file    
        ## specified   

        foreach($file in $files)   
        {       
            ## Skip the current file if it is the zip file itself      
            if($file.FullName -eq $zipName)    
            {          
                continue
            }
            ## Skip directories        
            if($file.PSIsContainer)      
            {           
                continue    
            }
            #讀取每個檔案進行壓縮
            try
            {
                #開啟檔案
                $originalFileStream = [System.IO.File]::Open($file.FullName,[System.IO.FileMode]::Open)
                $entry = New-Object ICSharpCode.SharpZipLib.Zip.ZipEntry($file.Name)
                
                $compressionStream.PutNextEntry($entry);
                $bytes = New-Object Byte[] $originalFileStream.Length
                #讀取檔案流
                $null = $originalFileStream.Read($bytes,0,$bytes.Length)
                #寫入到壓縮流
                $compressionStream.Write($bytes,0,$bytes.Length)
            }
            finally
            {
                $originalFileStream.Dispose()
            }
        }
    } 
    catch{
        $Error
        Remove-Item $Path
    }
    finally 
    {  
        ## Close the file   
        $compressionStream.Dispose()
        $compressedFileStream.Dispose()
        $compressionStream = $null 
        $compressedFileStream = $null 
    }
}
  1. 由於powershell2.0沒有內部的壓縮解壓方法。這裡使用.net的ICSharpCode.SharpZipLib.dll進行壓縮。
  2. powershell中可以通過New-Object Byte[]建立.net的位元組陣列,若建構函式需要傳參則直接將引數寫到後面即可。

該指令碼不支援帶資料夾的壓縮。

獲取上個版本的包

由於我定義的都是4位版本號,前三位是需求號,最後一位是修訂號。因此我直接通過前三位獲取上個版本的壓縮包檔案即可,若沒有,則無需建立差量更新包。


function Get-LastPackage($currentVersion,$majorVersion)
{
    #預設上個版本號就是當前版本號
    $lastFileName = $null
    $lastMajorVersion = $null
    $lastVersion = $null
    #讀取上一個版本的壓縮檔案
    # 獲取所有資料夾,程式目錄下的目錄都是版本號目錄
    $allFile = Get-ChildItem | Where-Object{ $_.Name -match '(\d*\.\d*\.\d*.\d*)\.zip' }
    if($null -eq $allFile) 
    {
        Write-Host "No Last Package"
        return
    }
    ## 獲取上一個版本號最新的修改
    $allFile = $allFile | Where-Object  {$_.Name -lt $majorVersion} | Sort-Object -descending
    ## 判斷是否有比當前版本小的
    if($null -eq $allFile)
    {
        ## 沒有歷史的大版本號,則全量更新,和當前版本的主版本號一樣
        Write-Host "No Last Package"
        return
    }
    #有多個檔案如2.25.0,2.24.1,則獲取到的是陣列
    elseif($allFile -is [array])
    {
        ##存在歷史更新,則獲取上一個版本的更新目錄
        $lastFileName  = $allFile[0]
    }
    #只有一個目錄,首個版本打包時則獲取到的是DirectoryInfo
    else
    {
        $lastFileName = $allFile
    }
    
    ## 獲取最新的版本
    $lastVersion =[System.IO.Path]::GetFileNameWithoutExtension($lastFileName)
    $lastMajorVersion = [System.IO.Path]::GetFileNameWithoutExtension($lastVersion)
    #返回檔名 主版本號 版本號
    $lastFileName
    $lastVersion
    $lastMajorVersion
}
  1. 我通過正則篩選出檔案格式為四位數字版本.zip的檔案。
  2. 然後對篩選出的檔名進行排序。
  3. 最後獲取比當前版本號小的上一個版本號。最後返回上個版本的完整檔案路徑及版本號資訊。

建立差量更新包

我只需要根據兩個檔案清單進行檔名和版本號的對比,如果同一個檔案的版本號一樣,則該檔案無需更新。


    $lastUnpackDir = UnZip $lastZipFullName $lastVersion
    $currentUpdateObject = Read-Json $currentUpdateListFile
    $lastUpdateObject = Read-Json $lastUpdateListFile
    $array = New-Object System.Collections.ArrayList
    #比較兩個清單檔案
    foreach($currentFile in $currentUpdateObject)
    {
        if($currentFile -eq  "FileUpdateList.txt") 
        {
            #跳過清單檔案
            continue
        }
        ##遍歷json陣列陣列物件本身也會遍歷,且值為空
    
        if($null -eq $currentFile.FileName) 
        {
            #跳過清單檔案
            continue
        }
        #當前清單每個檔案去上個版本查詢
        $lastFile = $lastUpdateObject | Where-Object  {$_.FileName -eq $currentFile.FileName} | Select-Object -First 1
        #找到檔案,判斷版本號
        if($lastFile.Version -eq $currentFile.Version)
        {
            #版本號一樣則不需要更新
            $sameFile =  Join-Path $currentUnpackDir $lastFile.FileName
            $null = $array.Add($sameFile)
            continue
        }
    }
  1. 先解壓出上個版本的壓縮檔案。
  2. 然後讀取兩個版本的檔案清單。
  3. 將一樣的檔案加入到一個列表中。

有了重複檔案的列表,接下來就可以將重複檔案都刪除。最後剩下差量更新的檔案

if($array.Count -eq $currentUpdateObject.Length - 1)
    {
        #所有都一樣,不需要更新
        Write-Host "No Modified File"
        return $false
    }
    else
    {
        #存在不一樣的包,則更新所有
        foreach($sameFile in $array)
        {
            Write-Host "Remove Not Modified File " $sameFile 
            Remove-Item $sameFile 
            #同時刪除pdb檔案
            $pdbFile = [System.IO.Path]::GetFileNameWithoutExtension($sameFile)+".pdb"
            if(Test-Path $pdbFile) 
            {
                Remove-Item $pdbFile 
            }
        }
        #重新獲取解壓的目錄
        $diffFiles = Get-ChildItem *.dll,*.exe
        #建立新的清單檔案
        Create-FileUpdateList -files $diffFiles -fileName $currentUpdateListFile
        #重新壓縮所有檔案
        $files = Get-ChildItem *.dll,*.pdb,*.exe,"FileUpdateList.txt"
        Write-Host "Need Update File " $files 
        $diffZipFullName = [System.IO.Path]::GetFileNameWithoutExtension($currentZipFullName)+"_diff.zip"
        New-ZipFile -files $files -Path $diffZipFullName -Force true
    }
    #移除上個版本的解壓出的壓縮資料夾
    Write-Host  "Remove Last Update Package dir" $lastUnpackDir
    Get-ChildItem $lastUnpackDir  | Remove-Item -Recurse
    Remove-Item $lastUnpackDir -Recurse
    $return $true
  1. 比較陣列的數量和當前讀取到的檔案數量是否一樣,若一致表示所有檔案都一樣,則無需更新,返回false表示沒有生成差量更新檔案。就不用建立差量更新檔案了。否則則刪除所有的檔案和對應的符號檔案。
  2. 然後將所有的差量檔案進行壓縮,檔案後面加上_diff表示是差量更新的檔案。
  3. 最後把解壓出來的上個版本的檔案和檔案加都刪除掉。返回true表示生成了差量更新檔案。

讀取伺服器Json配置

全量和差量檔案生成完畢後就可以將檔案上傳到指定的伺服器了。我將伺服器的配置資訊儲存到了ServerInfo.Json檔案中,這樣想新增其他伺服器只要修改一下這個配置檔案即可。讀取配置的伺服器,ServerInfo.Json包含了伺服器的一些資訊,包括地址,使用者名稱,及一些路徑配置。


$config = Read-Json "ServerInfo.json"

foreach($itemConfig in $config)
{
    Remote-CreateDic -userName $itemConfig.UserName -password $itemConfig.Password -address $itemConfig.Address -jobName $ftpFileName -programeDir $itemConfig.ProgramDir -ErrorAction Stop
    #目標 ftp://host:port/xxx/xxx.zip
    $destination = "ftp://"+$itemConfig.FTP.Host+":"+$itemConfig.FTP.Port+"/"+$ftpFileName 
    # FTP上傳壓縮包
    
    FTP-UpdateFile -file $zipFullName  -destination $destination -userName $itemConfig.FTP.UserName -password $itemConfig.FTP.Password -ErrorAction Stop
    
    if($hasDiffFile ){
        $ftpFileName = Join-Path $ENV:JOB_NAME ($version+"_diff.zip")
        FTP-UpdateFile -file $zipFullName  -destination $destination -userName $itemConfig.FTP.UserName -password $itemConfig.FTP.Password -ErrorAction Stop
    }
}
  1. 通過Remote-CreateDic執行遠端建立資料夾的命令。
  2. 通過FTP-UpdateFile將全量更新包和差量更新包都上傳到指定的伺服器上。

遠端建立資料夾目錄

在上傳到FTP上時,若有必要則需要先在FTP上建立指定的資料夾,避免上傳資料夾的時候由於沒有資料夾導致上傳失敗。
由於需要遠端呼叫,因此需要傳遞使用者,密碼和伺服器地址。同時還要包含jenkins當前的job名稱以及遠端伺服器程式上傳路徑。上傳的路徑約定為FTP地址/Job名稱/版本號.zip



function Remote-CreateDic()
{
    param([string] $userName=$(throw "Parameter missing: -userName"),
    [string] $password=$(throw "Parameter missing: -password"),
    [string] $address=$(throw "Parameter missing: -address"),
    [string] $jobName=$(throw "Parameter missing: -jobName"),
    [string] $programeDir=$(throw "Parameter missing: -programeDir"))
    
    #非域使用者需要新增\,否則遠端呼叫有問題
    if(!$userName.StartsWith('\'))
    {
        $userName="\"+$userName
    }
    $secpwd = convertto-securestring $password -asplaintext -force
    $cred = new-object System.Management.Automation.PSCredential -argumentlist $userName,$secpwd
    #程式存放目錄和當前的jenkins job目錄合併後是伺服器鎖在的FTP程式存放目錄
    $zipFile= [System.IO.Path]::Combine($programeDir,$jobName)

    #備份程式
    invoke-command -computername $address -Credential $cred -command {
        param([string]$file)
        #獲取資料夾路徑
        $dir = [System.IO.Path]::GetDirectoryName($file)
        if(Test-Path $dir){
            
            Write-Host "Dic exists :" $dir 
            #資料夾存在
            if(Test-Path $file)
            {
                #壓縮檔案已存在.不允許,版本號沒變。必須改變
                throw $file + "exists,retry after change version"
            }
        }
        else
        {
            # 判斷資料夾是否存在
            # 沒有資料夾則建立,否則首次FTP上傳沒有資料夾時則會上傳失敗
            #防止輸出
            $null = New-Item -Path $dir -Type Directory 
        }
    } -ArgumentList $zipFile -ErrorAction Stop

}

FTP上傳

FTP上傳可以呼叫.Net的WebClient上傳檔案的方法處理。

function FTP-UpdateFile()
{
    param([String]$file=$(throw "Parameter missing: -name file"),
    [String]$destination=$(throw "Parameter missing: -name destination"),
    [String]$userName=$(throw "Parameter missing: -name userName"),
    [String]$password=$(throw "Parameter missing: -name destination"))

    $pwd=ConvertTo-SecureString $password -AsPlainText -Force; #111111為密碼
    $cred=New-Object System.Management.Automation.PSCredential($userName,$pwd); #建立自動認證物件
    $wc = New-Object System.Net.WebClient 
    try
    {
        $wc.Credentials = $cred 
        Write-Host "upload to ftp. " $file "->" $destination
        $wc.UploadFile($destination, $file) 
        Write-Host "upload success "
    }
    finally
    {
        $wc.Dispose()
        Write-Host "close ftp. "
    }
}

總結

本文對Windows下的持續部署包建立和上傳的邏輯繼續了說明,主要通過自動生成的版本號繼續判斷與比較哪些庫包需要更新。最終將庫包通過FTP上傳到各個伺服器上。最後就可以呼叫各個伺服器的遠端指令碼進行服務的解除安裝與更新了。

  1. Windows PowerShell 入門
  2. Powershell變數的作用域
  3. Windows Remote Management
  4. windows伺服器遠端執行命令(PowerShell+WinRM)
  5. winServer-常用winrm命令
  6. Use Powershell to start a GUI program on a remote machine

本文地址:https://www.cnblogs.com/Jack-Blog/p/11255314.html
作者部落格:傑哥很忙
歡迎轉載,請在明顯位置給出出處及連結

相關文章