Questo articolo è puramente pratico e racconta la mia triste storia.
Preparandomi a Zero Touch PROD per RDS (MS SQL), di cui ci hanno parlato incessantemente, ho realizzato una presentazione (POC - Proof Of Concept) di automazione: un insieme di script powershell. Dopo la presentazione, quando sono svaniti gli applausi entusiasti che si sono trasformati in ovazioni senza fine, mi è stato detto: tutto ciò va bene, ma per motivi ideologici tutti i nostri Jenkins slaves funzionano su Linux!
È possibile? Prendere un DBA così affezionato da Windows e scaraventarvelo in mezzo al caotico powershell di Linux? Non è crudele?

Ho dovuto immergermi in questa strana combinazione di tecnologie. Naturalmente, tutti i miei oltre 30 script hanno smesso di funzionare. Con mia sorpresa, sono riuscito a sistemarli tutti in un giorno di lavoro. Scrivo mentre le cose sono ancora fresche. Quindi, quali insidie potreste incontrare nel trasferire gli script powershell da Windows a Linux?
sqlcmd vs Invoke-SqlCmd
Ricordo la differenza principale tra i due. La vecchia e cara utilità sqlcmd funziona anche su Linux, con funzionalità quasi identiche. La query viene passata tramite -Q, il file di input come -i, e l'output -o. Solo che i nomi dei file, ovviamente, sono case-sensitive. Se utilizzate -i, scrivete alla fine nel file:
GO
EXITSe alla fine non c'è EXIT, sqlcmd attenderà l'input, e se prima EXIT caricherà GO, l'ultima istruzione non verrà eseguita. Nell'output finirà tutto, selezioni, messaggi, print ecc.
Invoke-SqlCmd restituisce il risultato come DataSet, DataTables o DataRows. Quindi, se potete elaborare il risultato di una semplice selezione anche tramite sqlcmd, analizzandone l'output, è praticamente impossibile restituire qualcosa di complesso: per questo c'è Invoke-SqlCmd. Ma questa comanda ha anche le sue stranezze:
- Se le passate un file tramite -InputFile, allora EXIT non è necessario, inoltre, genera un errore di sintassi.
- -OutputFile no, il comando restituisce il risultato come oggetto.
- Per specificare il server ci sono due sintassi: -ServerInstance -Username -Password -Database e tramite -ConnectionString. Stranamente, nel primo caso non è possibile indicare una porta diversa da 1433.
- l'output di tipo PRINT, che si può semplicemente "catturare" sqlcmd, per Invoke-SqlCmd
- E soprattutto:
E questo è il problema principale. Solo a marzo questo cmdlet , e finalmente possiamo andare avanti!
Sostituzione di variabili
In sqlcmd c'è la sostituzione delle variabili tramite -v, ad esempio, così:
# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res
-v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmdNel codice SQL utilizziamo le sostituzioni:
set @spid=$(spid)
set @age=$(age)Ecco. In *nix le sostituzioni delle variabili non funzionano.. Il parametro -v viene ignorato. Con Invoke-SqlCmd ignorato -Variables. Sebbene il parametro che definisce le variabili venga ignorato, le sostituzioni funzionano: potete usare qualsiasi variabile della Shell. Tuttavia, mi sono arrabbiato con le variabili e ho deciso di non dipendere affatto da esse, e ho agito in modo grezzo e primitivo, fortunatamente gli script SQL sono brevi:
# 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"Come avrete capito, questo è già un test della versione UNIX.
Caricamento file
Nella versione Windows ogni operazione era accompagnata da un audit: eseguivamo sqlcmd, ottenevamo qualche errore nel file di output, allegavamo quel file alla tabella di audit. Fortunatamente il SQL server funzionava sullo stesso server di Jenkins, quindi si faceva più o meno così:
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
returnIn questo modo carichiamo l'intero file BCP e lo inseriamo nel campo nvarchar(max) della tabella di audit. Naturalmente, tutto questo sistema è crollato, poiché invece di SQL server ho ottenuto RDS, e BULK INSERT non funziona affatto su UNC a causa del tentativo di prendere un lock esclusivo sul file, e con RDS questo è destinato a fallire sin dall'inizio. Quindi ho deciso di cambiare il design del sistema, memorizzando l'audit riga per riga:
CREATE TABLE AuditOut (
ID int NULL,
TextLine nvarchar(max) NULL,
n int IDENTITY(1,1) PRIMARY KEY
)E scrivere in questa tabella così:
function WriteAudit([string]$Filename, [string]$ConnStr,
[string]$Tabname, [string]$Jobname)
{
# ottieni $lastid dell'ultima esecuzione -- omesso per l'articolo
#crea una griglia e popolala con i dati dal 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)
}
# scrivilo nella tabella
$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()
}
Per selezionare il contenuto, è necessario fare una selezione per ID, ordinando in base a n (identity).
Nel prossimo articolo tratterò più nel dettaglio come tutto ciò interagisce con Jenkins.
Fonte: habr.com
