Тази статия е чисто практическа и е посветена на моята тъжна история
Подготвяйки се за Zero Touch PROD за RDS (MS SQL), за който ни прехвърляха постоянно, направих презентация (POC — Proof Of Concept) на автоматизация: набор от скриптове на PowerShell. След презентацията, когато бурните, продължителни аплодисменти утихнаха и преминаха в неизменни овации, ми казаха — всичко това е страхотно, но по идеологически причини всички наши Jenkins slaves работят под Linux!
Възможно ли е това? Да вземеш такъв мил, уютен DBA от Windows и да го сложиш в самото огнище на PowerShell под Linux? Това не е ли жестоко?

Приключих да се потопя в тази странна комбинация от технологии. Разбира се, всичките ми 30+ скрипта престанаха да работят. За мое учудване, в рамките на един работен ден успях всичко да поправя. Пиша по горещи следи. И така, какви подводни камъни могат да срещнат при прехвърлянето на скриптове на PowerShell от Windows в Linux?
sqlcmd срещу Invoke-SqlCmd
Нека напомня основната разлика между тях. Старата добра утилита sqlcmd работи и под Linux с почти идентична функционалност. Заявката, която искаме да изпълним, подаваме с -Q, входният файл като -i, а изходът -o. Единствено имената на файловете, разбира се, са чувствителни към регистъра. Ако използвате -i, то в края на файла трябва да напишете:
GO
EXITАко в края няма EXIT, sqlcmd ще премине в режим на изчакване на вход, а ако преди EXIT няма да GO, то последната команда няма да се изпълни. В изходния файл попада целият изход, selects, съобщения, print и т.н.
Invoke-SqlCmd връща резултата под формата на DataSet, DataTables или DataRows. Затова, ако можете да обработите резултата от проста заявка и през sqlcmd, разглеждайки неговия изход, то изкарването на нещо по-сложно е практически невъзможно: за това има Invoke-SqlCmd. Но тази команда има и свои особености:
- Ако й подадете файл през -InputFile, то EXIT не е необходим, освен това, тя дава синтактична грешка
- -OutputFile не, командата ви връща резултата под формата на обект
- За указване на сървъра има два синтаксиса: -ServerInstance -Username -Password -Database и през -ConnectionString. Както и да е, в първия случай не е възможно да се укаже порт, различен от 1433.
- текстовият изход, тип PRINT, който е елементарен за улавяне sqlcmd, за Invoke-SqlCmd
- И най-важното:
И това е основният проблем. Само през март този cmdlet , и накрая можем да продължим напред!
Замяна на променливи
В sqlcmd има замяна на променливи с помощта на -v, например, така:
# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res
-v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmdВ скрипта на SQL използваме замени:
set @spid=$(spid)
set @age=$(age)Така е. В *nix замените на променливи не работят. Параметърът -v се игнорира. При Invoke-SqlCmd се игнорира. -Променливи. Въпреки че параметърът, който задава самите променливи, се игнорира, самите замени работят — можете да използвате всякакви променливи от Shell. Но се обидих на променливите и реших да не завися от тях изобщо, и постъпих грубо и примитивно, за щастие скриптовете на sql са кратки:
# prepend the parameters
"declare @age int, @spid int" | Add-Content "q.sql"
"set @spid=" + $spid | Add-Content "q.sql"
"set @age=" + $age | Add-Content "q.sql"
foreach ($line in Get-Content "Sqlserver/Automation/KillSpid.sql") {
$line | Add-Content "q.sql"
}
$cmd = "/opt/mssql-tools/bin/" + $conn + " -i q.sql -o res.log"Това, както разбрахте, е тест вече с unix версия.
Качване на файлове
В Window версията всяка операция беше придружена от одит: извършили sqlcmd, получавали някаква руган в output file, прилагали този файл към таблица с одит. За щастие SQL сървърът работеше на същия сървър, на който беше Jenkins, това се правеше по следния начин:
CREATE procedure AuditUpload
@id int, @filename varchar(256)
as
set nocount on
declare @sql varchar(max)
CREATE TABLE #multi (filer NVARCHAR(MAX))
set @sql='BULK INSERT #multi FROM '''+@filename
+''' WITH (ROWTERMINATOR = '' '',CODEPAGE = ''ACP'')'
exec (@sql)
select @sql=filer from #multi
update JenkinsAudit set multiliner=@sql where ID=@id
returnПо този начин захващаме файла BCP изцяло и го вкарваме в полето nvarchar(max) на таблицата с одит. Разбира се, цялата тази система се разпадна, тъй като вместо SQL сървър получих RDS, а BULK INSERT изобщо не работи по UNC заради опитите да вземе ексклузивен лок на файла, а с RDS това от самото начало е обречено. Така че реших да променя дизайна на системата, съхранявайки одита ред по ред:
CREATE TABLE AuditOut (
ID int NULL,
TextLine nvarchar(max) NULL,
n int IDENTITY(1,1) PRIMARY KEY
)И да пиша в тази таблица така:
function WriteAudit([string]$Filename, [string]$ConnStr,
[string]$Tabname, [string]$Jobname)
{
# get $lastid of the last execution -- пропуснато за статията
#създайте мрежа и я попълнете с данни от файла
$audit = Get-Content $Filename
$DT = new-object Data.DataTable
$COL1 = new-object Data.DataColumn;
$COL1.ColumnName = "ID";
$COL1.DataType = [System.Type]::GetType("System.Int32")
$COL2 = new-object Data.DataColumn;
$COL2.ColumnName = "TextLine";
$COL2.DataType = [System.Type]::GetType("System.String")
$DT.Columns.Add($COL1)
$DT.Columns.Add($COL2)
foreach ($line in $audit)
{
$DR = $dt.NewRow()
$DR.Item("ID") = $lastid
$DR.Item("TextLine") = $line
$DT.Rows.Add($DR)
}
# write it to table
$conn=new-object System.Data.SqlClient.SQLConnection
$conn.ConnectionString = $ConnStr
$conn.Open()
$bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $ConnStr
$bulkCopy.DestinationTableName = $Tabname
$bulkCopy.BatchSize = 50000
$bulkCopy.BulkCopyTimeout = 0
$bulkCopy.WriteToServer($DT)
$conn.Close()
}
За да изберете съдържанието, трябва да направите select по ID, избирайки в реда n (идентификатор).
В следващата статия ще се спра по-подробно на това как всичко това взаимодейства с Jenkins.
Източник: habr.com
