Este artigo é puramente práctico e está dedicado á miña triste historia
Preparándonos para PROD Zero Touch para RDS (MS SQL), sobre o que todos os nosos oídos zumbaban, fixen unha presentación (POC - Proof Of Concept) de automatización: un conxunto de scripts de powershell. Despois da presentación, cando os tormentosos e prolongados aplausos apagaron, converténdose en aplausos incesantes, dixéronme: todo isto é bo, pero só por razóns ideolóxicas, todos os nosos escravos de Jenkins traballan en Linux.
Isto é posible? Tomar un DBA tan cálido e lámpado de Windows e pégalo no mesmo calor do Powershell baixo Linux? Non é cruel?
Tiven que mergullarme nesta estraña combinación de tecnoloxías. Por suposto, todos os meus máis de 30 scripts deixaron de funcionar. Para a miña sorpresa, conseguín arranxar todo nun día laborable. Escribo en busca. Entón, que trampas podes atopar ao transferir scripts de Powershell de Windows a Linux?
sqlcmd vs Invoke-SqlCmd
Permíteme recordarche a principal diferenza entre eles. Boa utilidade antiga sqlcmd Tamén funciona baixo Linux, cunha funcionalidade case idéntica. Pasamos a consulta para executar -Q, o ficheiro de entrada como -i e a saída como -o. Pero os nomes dos ficheiros, por suposto, distinguen entre maiúsculas e minúsculas. Se usas -i, escribe ao final no ficheiro:
GO
EXIT
Se non hai EXIT ao final, entón sqlcmd procederá a esperar a entrada, e se antes SAIR non o fará GO, entón o último comando non funcionará. O ficheiro de saída contén toda a saída, seleccións, mensaxes, impresión, etc.
Invoke-SqlCmd produce o resultado como DataSet, DataTables ou DataRows. Polo tanto, se procesa o resultado dunha simple selección, pode usar sqlcmd, unha vez analizada a súa saída, é case imposible derivar algo complexo: para iso hai Invocar-SqlCmd. Pero este equipo tamén ten as súas propias bromas:
Se transfire un ficheiro a ela vía -Ficheiro de entrada, Entón SAIR non é necesario, ademais, produce un erro de sintaxe
-Ficheiro de saída non, o comando devolveche o resultado como un obxecto
Hai dúas sintaxes para especificar un servidor: -ServerInstance -Nome de usuario -Contrasinal -Base de datos e a través -Cadea de conexión. Curiosamente, no primeiro caso non é posible especificar un porto que non sexa 1433.
saída de texto, escriba PRINT, que simplemente é "capturado" sqlcmdpor Invocar-SqlCmdé un problema
sqlcmd ten substitución de variables usando -v, por exemplo como este:
# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res
-v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmd
No script SQL usamos substitucións:
set @spid=$(spid)
set @age=$(age)
Entón, aquí está. En *nix as substitucións de variables non funcionan... Parámetro -v ignorado. U Invocar-SqlCmd ignorado -Variables. Aínda que se ignora o parámetro que especifica as propias variables, as propias substitucións funcionan; podes usar calquera variable de Shell. Non obstante, ofendéronme as variables e decidín non depender delas en absoluto, e actuei de forma groseira e primitiva, xa que os scripts SQL son curtos:
Esta, como entendes, é unha proba xa da versión de Unix.
Cargando ficheiros
Na versión de Windows, calquera operación ía acompañada dunha auditoría: executamos sqlcmd, recibimos algún tipo de abuso no ficheiro de saída, adxuntamos este ficheiro á placa de auditoría. Afortunadamente, o servidor SQL funcionou no mesmo servidor que Jenkins, fíxose algo así:
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
Así, tragamos o ficheiro BCP por completo e o introducimos no campo nvarchar(max) da táboa de auditoría. Por suposto, todo este sistema desmoronouse, porque en lugar dun servidor SQL conseguín RDS, e BULK INSERT non funciona en absoluto a través de UNC debido a un intento de bloquear un ficheiro exclusivo, e con RDS isto é xeralmente condenado. o comezo mesmo. Entón decidín cambiar o deseño do sistema, almacenando a auditoría liña por liña:
CREATE TABLE AuditOut (
ID int NULL,
TextLine nvarchar(max) NULL,
n int IDENTITY(1,1) PRIMARY KEY
)
E escribe nesta táboa así:
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()
}
Para seleccionar contido, cómpre seleccionar por ID, escollendo por orde n (identidade).
No seguinte artigo vou entrar en máis detalles sobre como todo isto interactúa con Jenkins.