目录
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编译成功,先获取所有exe,dll,获取他们的版本号,创建文件更新清单。
- 将所有的exe,dll,pdb以及文件更新清单进行压缩打包,压缩文件名为指定主程序的版本号。
- 将上个版本的文件压缩包解压,若没有上个版本的文件压缩包,则无需上传差量更新包。
- 比较当前文件更新清单和上个版本的文件更新清单。若文件名一样,版本号也一样,则删除编译出的对应文件。
- 获取剩下的所有exe,dll,pdb以及文件更新清单进行压缩打包,压缩文件名为指定主程序的版本号_Diff。
- 遍历每个服务配置。
- 获取上传的相对目录为Job名/版本号.zip,调用远程命令,检查服务器是否存在目录,没有的话则创建目录。
- 目录创建完毕后,将压缩文件上传,若有差量更新包则也需要上传。
- 最后调用远程命令对服务进行卸载与更新。
生成版本号
上一章介绍了.net
环境下如何自动生成版本号的逻辑,这里不再介绍。有兴趣的可以看《Jenkins持续部署-自动生成版本号
》
获取版本号
$version =(Get-ChildItem $executeFile).VersionInfo.FileVersion
$executeFile
为可执行的exe文件。通过Get-ChildItem 获取到该文件。通过
VersionInfo.FileVersion
或VersionInfo.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
}
- 通过
Get-ChildItem *.dll,*.exe
获取当前目录所有的dll和exe文件 - 将更新清单文件名设置为
FileUpdateList.txt
,若已经存在更新清单文件,则删除旧的重新生成。 - 通过
New-Object
创建一个.Net的集合,用于存放每个文件的文件信息。 $fileInfo="" | Select-Object -Property FileName,Version
定义了一个自定义对象类型,包含了文件名和版本号2个属性。- 将每个文件的文件名和版本号保存到集合中
- 将集合转化为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
}
}
- 由于
powershell2.0
没有内部的压缩解压方法。这里使用.net的ICSharpCode.SharpZipLib.dll
进行压缩。 - 在
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
}
- 我通过正则筛选出文件格式为
四位数字版本.zip
的文件。 - 然后对筛选出的文件名进行排序。
- 最后获取比当前版本号小的上一个版本号。最后返回上个版本的完整文件路径及版本号信息。
创建差量更新包
我只需要根据两个文件清单进行文件名和版本号的对比,如果同一个文件的版本号一样,则该文件无需更新。
$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
}
}
- 先解压出上个版本的压缩文件。
- 然后读取两个版本的文件清单。
- 将一样的文件加入到一个列表中。
有了重复文件的列表,接下来就可以将重复文件都删除。最后剩下差量更新的文件
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
- 比较数组的数量和当前读取到的文件数量是否一样,若一致表示所有文件都一样,则无需更新,返回
false
表示没有生成差量更新文件。就不用创建差量更新文件了。否则则删除所有的文件和对应的符号文件。 - 然后将所有的差量文件进行压缩,文件后面加上_diff表示是差量更新的文件。
- 最后把解压出来的上个版本的文件和文件加都删除掉。返回
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
}
}
- 通过
Remote-CreateDic
执行远程创建文件夹的命令。 - 通过
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上传到各个服务器上。最后就可以调用各个服务器的远程脚本进行服务的卸载与更新了。
- Windows PowerShell 入门
- Powershell变量的作用域
- Windows Remote Management
- windows服务器远程执行命令(PowerShell+WinRM)
- winServer-常用winrm命令
- Use Powershell to start a GUI program on a remote machine