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编译完成之后,我们需要处理以下事项。

Jenkins持续部署-创建差量更新包-LMLPHP

  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

07-28 00:31