Acest articol este pur practic și este dedicat poveștii mele triste
Pregătindu-mă pentru Zero Touch PROD RDS (MS SQL), despre care ne-au tot vorbit, am realizat o prezentare (POC - Proof Of Concept) a automatizării: un set de scripturi PowerShell. După prezentare, când aplauzele entuziaste au încetat, iar ovațiile au continuat, mi s-a spus - toate acestea sunt bune, dar din motive ideologice, toate Jenkins slaves functionează pe Linux!
Se poate așa ceva? Să iei un DBA atât de cald și primitor din Windows și să-l arunci în mijlocul tumultului PowerShell pe Linux? Nu este oare aceasta o cruzime?

A trebuit să mă adâncesc în această combinație ciudată de tehnologii. Desigur, toate cele 30+ scripturi ale mele au încetat să funcționeze. Spre surpriza mea, într-o singură zi lucrătoare am reușit să le repar pe toate. Scriu pe căldura momentului. Așadar, ce capcane ar putea apărea atunci când transferați scripturi PowerShell din Windows în Linux?
sqlcmd vs Invoke-SqlCmd
Îmi amintesc principala diferență dintre ele. Vechea utilitate sqlcmd funcționează și pe Linux, cu o funcționalitate aproape identică. Interogarea pentru execuție se transmite prin -Q, fișierul de intrare ca -i, iar outputul -o. Doar că numele fișierelor, desigur, devin sensibile la litere mari și mici. Dacă folosiți -i, atunci în fișier scrieți la final:
GO
EXITDacă la final nu va fi EXIT, atunci sqlcmd va aștepta o introducere, iar dacă înainte de EXIT va GO, atunci ultima comandă nu va fi executată. În fișierul de output ajunge întreaga ieșire, selects, mesaje, print etc.
Invoke-SqlCmd returnează rezultatul sub formă de DataSet, DataTables sau DataRows. Prin urmare, dacă puteți prelucra rezultatul unui select simplu și prin sqlcmd, descompunând ieșirea sa, atunci să obțineți ceva complex este practic imposibil: pentru aceasta există Invoke-SqlCmd. Dar această comandă are și hibele ei:
- Dacă îi transmiteți un fișier prin -InputFile, atunci EXIT nu este necesar, de fapt, dă o eroare de sintaxă
- -OutputFile nu, comanda vă returnează rezultatul sub formă de obiect
- Pentru a specifica serverul, există două sintaxuri: -ServerInstance -Username -Password -Database și prin -ConnectionString. Cumva ciudat, în primul caz nu se poate specifica un port diferit de 1433.
- ieșirea text, de tip PRINT, care este ușor 'prinsă' sqlcmd, pentru Invoke-SqlCmd
- Și cel mai important:
Și aceasta este problema principală. Doar în martie acest cmdlet , și în sfârșit putem merge înainte!
Substituția variabilelor
În sqlcmd există substituția variabilelor folosind -v, de exemplu, așa:
# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res
-v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmdÎn scriptul SQL folosim substituții:
set @spid=$(spid)
set @age=$(age)Așadar, în *nix substituțiile variabilelor nu funcționează. Parametrul -v este ignorat. La Invoke-SqlCmd este ignorat -Variabile. Deși parametrul care definește variabilele este ignorat, substituțiile funcționează — puteți folosi orice variabile din Shell. Totuși, m-am supărat pe variabile și am decis să nu depind de ele deloc, și am acționat brut și primitiv, având în vedere că scripturile SQL sunt scurte:
# 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"Acesta, așa cum ați înțeles, este un test de versiune unix.
Încărcarea fișierelor
În versiunea Windows, orice operație era însoțită de audit: am rulat sqlcmd, am primit niște erori în fișierul de output, am anexat acel fișier la tabelul de audit. Din fericire, SQL Server funcționa pe același server cu Jenkins, se realiza cam așa:
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
returnAstfel, încărcăm fișierul BCP în întregime și îl introducem în câmpul nvarchar(max) al tabelului de audit. Desigur, întregul sistem s-a prăbușit, deoarece în loc de SQL Server am primit RDS, iar BULK INSERT nu funcționează în mod UNC din cauza încercării de a lua un lock exclusiv pe fișier, iar cu RDS aceasta era oricum destinată eșecului. Așadar, am decis să schimb designul sistemului, stocând auditul pe fiecare linie:
CREATE TABLE AuditOut (
ID int NULL,
TextLine nvarchar(max) NULL,
n int IDENTITY(1,1) PRIMARY KEY
)Și să scriem în acest tabel astfel:
function WriteAudit([string]$Filename, [string]$ConnStr,
[string]$Tabname, [string]$Jobname)
{
# obțineți $lastid al ultimei execuții -- omis pentru articol
#creați un grid și umpleți-l cu date din fișier
$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)
}
# scrieți în tabel
$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()
}
Pentru a selecta conținutul, trebuie să faceți select pe ID, selectând în ordinea n (identity).
În următorul articol, voi detalia mai mult cum interacționează toate acestea cu Jenkins.
Sursa: habr.com
