Ovaj članak je čisto praktičan i posvećen je mojoj tužnoj priči
Priprema za Zero Touch PROD za RDS (MS SQL), o kojem su nam sve uši zujale, napravio sam prezentaciju (POC - Proof Of Concept) automatizacije: set powershell skripti. Nakon prezentacije, kad je buran, dugotrajni pljesak utihnuo, prerastajući u neprestani pljesak, rekli su mi – sve je to dobro, ali samo iz ideoloških razloga, svi naši Jenkinsovi robovi rade na Linuxu!
Je li to moguće? Uzeti tako toplog, lampaškog DBA ispod Windowsa i zabiti ga u samu vrućinu powershell-a pod Linuxom? Nije li ovo okrutno?
Morao sam uroniti u ovu čudnu kombinaciju tehnologija. Naravno, svih mojih 30+ skripti su prestale raditi. Na moje iznenađenje, uspio sam sve popraviti u jednom radnom danu. Pišem u hitnoj potrazi. Dakle, na koje se zamke možete susresti prilikom prijenosa powershell skripti iz Windowsa u Linux?
sqlcmd u odnosu na Invoke-SqlCmd
Dopustite mi da vas podsjetim na glavnu razliku između njih. Dobri stari alat sqlcmd Također radi pod Linuxom, s gotovo identičnom funkcionalnošću. Prosljeđujemo upit za izvršenje -Q, ulaznu datoteku kao -i, a izlaz kao -o. Ali nazivi datoteka, naravno, razlikuju velika i mala slova. Ako koristite -i, tada u datoteku na kraju napišite:
GO
EXIT
Ako nema EXIT na kraju, tada će sqlcmd nastaviti čekati unos, a ako prije EXIT neće GO, tada zadnja naredba neće raditi. Izlazna datoteka sadrži sav izlaz, odabire, poruke, ispis itd.
Invoke-SqlCmd proizvodi rezultat kao DataSet, DataTables ili DataRows. Stoga, ako obradite rezultat jednostavnog odabira, možete koristiti sqlcmd, analizirajući njegov izlaz, gotovo je nemoguće izvesti nešto složeno: za ovo postoji Invoke-SqlCmd. Ali ova ekipa ima i svoje šale:
Ako joj prenesete datoteku putem -Ulazna datotekatada EXIT nije potreban, štoviše, proizvodi sintaktičku pogrešku
-Izlazna datoteka ne, naredba vam vraća rezultat kao objekt
Postoje dvije sintakse za određivanje poslužitelja: -Instanca poslužitelja -Korisničko ime -Lozinka -Baza podataka i kroz -ConnectionString. Čudno je da u prvom slučaju nije moguće navesti port osim 1433.
izlaz teksta, tip PRINT, koji se jednostavno “hvata” sqlcmdza Invoke-SqlCmdje problem
sqlcmd ima zamjenu varijable koristeći -v, na primjer ovako:
# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res
-v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmd
U SQL skripti koristimo zamjene:
set @spid=$(spid)
set @age=$(age)
Pa evo ga. U *nix zamjene varijabli ne rade. Parametar -v ignorirani. U Invoke-SqlCmd ignorirani -Varijable. Iako se parametar koji specificira same varijable zanemaruje, same zamjene rade - možete koristiti bilo koje varijable iz Shella. Međutim, uvrijedile su me varijable i odlučio sam uopće ne ovisiti o njima, te sam se ponašao grubo i primitivno, jer su SQL skripte kratke:
Ovo je, kao što razumijete, test već iz Unix verzije.
Prijenos datoteka
U verziji sustava Windows svaka je operacija bila popraćena revizijom: pokrenuli smo sqlcmd, primili neku vrstu zlouporabe u izlaznoj datoteci, priložili ovu datoteku revizijskoj ploči. Srećom, SQL poslužitelj je radio na istom poslužitelju kao Jenkins, učinjeno je otprilike ovako:
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
Stoga u cijelosti gutamo BCP datoteku i guramo je u polje nvarchar(max) revizijske tablice. Naravno, cijeli ovaj sustav se raspao jer sam umjesto SQL servera dobio RDS, a preko UNC-a BULK INSERT nikako ne radi zbog pokušaja ekskluzivnog zaključavanja datoteke, a kod RDS-a je to općenito osuđeno na propast samog početka. Stoga sam odlučio promijeniti dizajn sustava, pohranjujući reviziju red po red:
CREATE TABLE AuditOut (
ID int NULL,
TextLine nvarchar(max) NULL,
n int IDENTITY(1,1) PRIMARY KEY
)
I upišite u ovu tablicu ovako:
function WriteAudit([string]$Filename, [string]$ConnStr,
[string]$Tabname, [string]$Jobname)
{
# get $lastid of the last execution -- проскипано для статьи
#create grid and populate it with data from file
$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()
}
Za odabir sadržaja potrebno je odabrati prema ID-u, birajući redom n (identitet).
U sljedećem ću članku detaljnije govoriti o tome kako sve to djeluje s Jenkinsom.