Este artigo é puramente prático e dedicado à minha triste história
Preparando-se para Zero Touch PROD para RDS (MS SQL), sobre o qual nós fomos incansavelmente avisados, eu fiz uma apresentação (POC - Proof Of Concept) de automação: uma coleção de scripts PowerShell. Após a apresentação, quando os aplausos entusiásticos, que duraram muito tempo, se acalmaram e se transformaram em uma ovação interminável, me disseram — tudo isso é ótimo, mas por razões ideológicas, todos os nossos slaves do Jenkins funcionam em Linux!
Como assim? Pegar um DBA tão afetuoso, acostumado ao Windows, e jogá-lo no calor do PowerShell no Linux? Isso não é cruel?

Tive que me imergir nessa estranha combinação de tecnologias. Claro, todos os meus 30+ scripts pararam de funcionar. Para minha surpresa, em um dia de trabalho, consegui corrigir tudo. Estou escrevendo enquanto os acontecimentos ainda são frescos. Então, quais armadilhas podem aparecer ao transferir scripts PowerShell do Windows para o Linux?
sqlcmd vs Invoke-SqlCmd
Vou lembrar a principal diferença entre eles. A velha e boa ferramenta sqlcmd também funciona em Linux, com funcionalidade quase idêntica. Passamos a consulta a ser executada com -Q, o arquivo de entrada como -i, e a saída -o. Apenas os nomes dos arquivos, naturalmente, são sensíveis a maiúsculas e minúsculas. Se você usar -i, então no final do arquivo você deve escrever:
GO
EXITSe não houver EXIT no final, o sqlcmd entrará em espera por entrada, e se antes de EXIT não funcionará GO, a última comando não será executada. Todo o output, incluindo selects, mensagens, print, etc., vai para o arquivo de saída.
Invoke-SqlCmd retorna o resultado na forma de DataSet, DataTables ou DataRows. Portanto, se você puder processar o resultado de um select simples através de sqlcmd, analisando sua saída, é praticamente impossível retornar algo complexo: para isso existe Invoke-SqlCmd. Mas esta command tem seus próprios truques:
- Se você passar um arquivo através de -InputFile, então EXIT não é necessário, além disso, ele retorna um erro de sintaxe
- -OutputFile não, o comando retorna o resultado como um objeto
- Existem duas sintaxes para especificar o servidor: -ServerInstance -Username -Password -Database e através de -ConnectionString. Curiosamente, no primeiro caso, não é possível especificar uma porta diferente de 1433.
- a saída em texto, como PRINT, que é facilmente "capturada" sqlcmd, para Invoke-SqlCmd
- E o principal:
E esse é o maior problema. Apenas em março, esse cmdlet , e finalmente podemos avançar!
Substituição de variáveis
No sqlcmd, há substituição de variáveis usando -v, por exemplo, assim:
# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res
-v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmdNo script SQL, usamos substituições:
set @spid=$(spid)
set @age=$(age)Assim, no *nix as substituições de variáveis não funcionam. Parâmetro -v é ignorado. Em Invoke-SqlCmd é ignorado -Variáveis. Embora o parâmetro que define as próprias variáveis seja ignorado, as substituições funcionam — você pode usar quaisquer variáveis do Shell. No entanto, eu fiquei chateado com as variáveis e decidi não depender delas, e optei por um método mais rudimentar, ainda bem que os scripts em sql são curtos:
# 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"Isso, como você percebeu, é um teste já a partir da versão unix.
Carregamento de arquivos
Na versão Windows, qualquer operação era acompanhada de auditoria: executei sqlcmd, recebi algum erro no arquivo de saída, anexei esse arquivo à tabela de auditoria. Ainda bem que o SQL Server estava no mesmo servidor que o Jenkins, isso era feito aproximadamente assim:
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
returnDessa forma, absorvemos o arquivo BCP por completo, e inserimos no campo nvarchar(max) da tabela de auditoria. É claro que todo esse sistema desmoronou, pois ao invés do SQL Server, recebi o RDS, e o BULK INSERT não funciona via UNC devido à tentativa de obter um bloqueio exclusivo no arquivo, e com o RDS isso estava fadado ao fracasso desde o início. Então, decidi alterar o design do sistema, armazenando a auditoria linha a linha:
CREATE TABLE AuditOut (
ID int NULL,
TextLine nvarchar(max) NULL,
n int IDENTITY(1,1) PRIMARY KEY
)E gravar nesta tabela assim:
function WriteAudit([string]$Filename, [string]$ConnStr,
[string]$Tabname, [string]$Jobname)
{
# obter $lastid da última execução -- pulado para o artigo
#criar grade e preenchê-la com dados do arquivo
$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)
}
# gravar na tabela
$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 selecionar o conteúdo, é necessário fazer select por ID, escolhendo na ordem de n (identidade).
No próximo artigo, falarei mais detalhadamente sobre como tudo isso interage com o Jenkins.
Fonte: habr.com
