ืืจื‘ืขื˜ืŸ ืžื™ื˜ MS SQL ืคึฟื•ืŸ Powershell ืื•ื™ืฃ ืœื™ื ื•ืงืก

ื“ืขืจ ืึทืจื˜ื™ืงืœ ืื™ื– ืจื™ื™ืŸ ืคึผืจืึทืงื˜ื™ืฉ ืื•ืŸ ืื™ื– ื“ืขื“ืึทืงื™ื™ื˜ืึทื“ ืฆื• ืžื™ื™ืŸ ื˜ืจื•ื™ืขืจื™ืง ื’ืขืฉื™ื›ื˜ืข

ื’ืจื™ื™ื˜ ื–ื™ืš ืคึฟืึทืจ ื ื•ืœ ืจื™ืจ ืคึผืจืึธื“ ืคึฟืึทืจ RDS (MS SQL), ื•ื•ืขื’ืŸ ื•ื•ืึธืก ืึทืœืข ืื•ื ื“ื–ืขืจ ืื•ื™ืขืจืŸ ื”ืึธื‘ืŸ ื‘ืึทื–ื™ื ื’, ืื™ืš ื’ืขืžืื›ื˜ ืึท ืคึผืจืขื–ืขื ื˜ื™ืจื•ื ื’ (POC - Proof Of Concept) ืคื•ืŸ ืึธื˜ืึทืžื™ื™ืฉืึทืŸ: ืึท ื’ืึทื ื’ ืคื•ืŸ ืคึผืึธื•ื•ืขืจืฉืขืœืœ ืกืงืจื™ืคึผืก. ื ืึธืš ื“ืขืจ ืคึผืจืขื–ืขื ื˜ื™ืจื•ื ื’, ื•ื•ืขืŸ ื“ื™ ืฉื˜ื•ืจืขืžื“ื™ืงืข, ืœืึทื ื’ืข ืึทืคึผืœืึธื“ื™ืกืžืขื ื˜ืŸ ื–ืขื ืขืŸ ืื•ื™ืกื’ืขืฉื˜ืึธืจื‘ืŸ, ืคืึทืจื•ื•ืึทื ื“ืœื˜ ืื™ืŸ ืื•ืžืึธืคึผื”ืขื ื’ื™ืงืข ืึทืคึผืœืึธื“ื™ืกืžืขื ื˜ืŸ, ื”ืึธื‘ืŸ ื–ื™ื™ ืžื™ืจ ื’ืขื–ืึธื’ื˜ - ื“ืึธืก ืึทืœืฅ ืื™ื– ื’ื•ื˜, ื ืึธืจ ืฆื•ืœื™ื‘ ืื™ื“ืขืึธืœืึธื’ื™ืฉืข ืกื™ื‘ื•ืช, ืึทืจื‘ืขื˜ืŸ ืึทืœืข ืื•ื ื“ื–ืขืจืข ื“ื–ืฉืขื ืงื™ื ืก ืฉืงืœืึทืคึฟืŸ ืื•ื™ืฃ ืœื™ื ื•ืงืก!

ืื™ื– ื“ืึธืก ืžืขื’ืœืขืš? ื ืขืžืขืŸ ืึทื–ืึท ืึท ื•ื•ืึทืจืขื, ืœืึธืžืคึผ ื“ื‘ืึท ืคึฟื•ืŸ ืื•ื ื˜ืขืจ Windows ืื•ืŸ ืฉื˜ืขืงืŸ ืขืก ืื™ืŸ ื“ื™ ื”ื™ืฅ ืคื•ืŸ ืคึผืึธื•ื•ืขืจืฉืขืœ ืื•ื ื˜ืขืจ ืœื™ื ื•ืงืก? ืื™ื– ื“ืืก ื ื™ืฉื˜ ืื›ื–ืจ?

ืืจื‘ืขื˜ืŸ ืžื™ื˜ MS SQL ืคึฟื•ืŸ Powershell ืื•ื™ืฃ ืœื™ื ื•ืงืก
ืื™ืš ื”ืื˜ ืฆื• ื™ื™ึทื ื˜ื•ื ืงืขืŸ ื–ื™ืš ืื™ืŸ ื“ืขื ืžืึธื“ื ืข ืงืึธืžื‘ื™ื ืึทืฆื™ืข ืคื•ืŸ โ€‹โ€‹ื˜ืขืงื ืึทืœืึทื“ื–ืฉื™ื–. ืคื•ืŸ ืงื•ืจืก, ืึทืœืข ืžื™ื™ืŸ 30+ ืกืงืจื™ืคึผืก ืคืืจืฉื˜ืืคื˜ ืืจื‘ืขื˜ืŸ. ืฆื• ืžื™ื™ืŸ ื™ื‘ืขืจืจืึทืฉืŸ, ืื™ืš ื’ืขืจืื˜ืŸ ืฆื• ืคืึทืจืจื™ื›ื˜ืŸ ืึทืœืฅ ืื™ืŸ ืื™ื™ืŸ ืึทืจื‘ืขื˜ ื˜ืึธื’. ืื™ืš ืฉืจืฒึทื‘ ืื™ืŸ ื”ื™ื™ืกืŸ ื™ืึธื’. ืึทื–ื•ื™, ื•ื•ืึธืก ืคึผื™ื˜ืคืึธืœื– ืงืขื ืขืŸ ืื™ืจ ื˜ืจืขืคืŸ ื•ื•ืขืŸ ื˜ืจืึทื ืกืคืขืจื™ื ื’ ืคึผืึธื•ื•ืขืจืฉืขืœืœ ืกืงืจื™ืคึผืก ืคื•ืŸ Windows ืฆื• ืœื™ื ื•ืงืก?

sqlcmd ื•ื•ืก Invoke-SqlCmd

ื–ืืœ ืžื™ืจ ื“ืขืจืžืึธื ืขืŸ ื“ื™ ื”ื•ื™ืคึผื˜ ื—ื™ืœื•ืง ืฆื•ื•ื™ืฉืŸ ื–ื™ื™. ื’ื•ื˜ ืึทืœื˜ ื ื•ืฆืŸ sqlcmd ืขืก ืื•ื™ืš ืึทืจื‘ืขื˜ ืื•ื ื˜ืขืจ ืœื™ื ื•ืงืก, ืžื™ื˜ ื›ึผืžืขื˜ ื™ื™ื“ืขื ื™ืงืึทืœ ืคืึทื ื’ืงืฉืึทื ืึทืœื™ื˜ื™. ืžื™ืจ ืคืึธืจืŸ ื“ื™ ืึธื ืคึฟืจืขื’ ืฆื• ื•ื™ืกืคื™ืจืŸ -Q, ื“ื™ ืึทืจื™ื™ึทื ืฉืจื™ื™ึทื‘ ื˜ืขืงืข ื•ื•ื™ -i, ืื•ืŸ ื“ืขืจ ืจืขื–ื•ืœื˜ืึทื˜ ื•ื•ื™ -ืึธ. ืื‘ืขืจ ื“ื™ ื˜ืขืงืข ื ืขืžืขืŸ, ืคื•ืŸ ืงื•ืจืก, ื–ืขื ืขืŸ ื’ืขืžืื›ื˜ ืคืึทืœ-ืฉืคึผื™ืจืขื•ื•ื“ื™ืง. ืื•ื™ื‘ ืื™ืจ ื ื•ืฆืŸ -i, ืื™ืŸ ื“ื™ ื˜ืขืงืข ืฉืจื™ื™ึทื‘ืŸ ืื™ืŸ ื“ื™ ืกื•ืฃ:

GO
EXIT

ืื•ื™ื‘ ืขืก ืื™ื– ืงื™ื™ืŸ EXIT ืื™ืŸ ื“ื™ ืกื•ืฃ, sqlcmd ื•ื•ืขื˜ ืคืึธืจื–ืขืฆืŸ ืฆื• ื•ื•ืึทืจื˜ืŸ ืคึฟืึทืจ ืึทืจื™ื™ึทื ืฉืจื™ื™ึทื‘, ืื•ืŸ ืื•ื™ื‘ ืคืจื™ืขืจ ืึทืจื•ื™ืกื’ืึทื ื’ ื•ื•ืขื˜ ื ื™ื˜ GO, ื“ืขืžืึธืœื˜ ื“ืขืจ ืœืขืฆื˜ืข ื‘ืึทืคึฟืขืœ ื•ื•ืขื˜ ื ื™ืฉื˜ ืึทืจื‘ืขื˜ืŸ. ื“ืขืจ ืจืขื–ื•ืœื˜ืึทื˜ ื˜ืขืงืข ื›ึผื•ืœืœ ืึทืœืข ื“ื™ ืคึผืจืึธื“ื•ืงืฆื™ืข, ืกืึทืœืขืงืฅ, ืึทืจื˜ื™ืงืœืขืŸ, ื“ืจื•ืงืŸ, ืขื˜ืง.

Invoke-SqlCmd ื˜ืจืื’ื˜ ื“ืขืจ ืจืขื–ื•ืœื˜ืึทื˜ ื•ื•ื™ ืึท DataSet, DataTables ืึธื“ืขืจ DataRows. ื“ืขืจื™ื‘ืขืจ, ืื•ื™ื‘ ืื™ืจ ืคึผืจืึธืฆืขืก ื“ืขืจ ืจืขื–ื•ืœื˜ืึทื˜ ืคื•ืŸ ืึท ืคึผืฉื•ื˜ ืื•ื™ืกืงืœื™ื™ึทื‘ืŸ, ืื™ืจ ืงืขื ืขืŸ ื ื•ืฆืŸ sqlcmd, ื ืึธืš ืคึผืึทืจืกืขื“ ื–ื™ื™ึทืŸ ืจืขื–ื•ืœื˜ืึทื˜, ืขืก ืื™ื– ื›ึผืžืขื˜ ืื•ืžืžืขื’ืœืขืš ืฆื• ื‘ืึทืงื•ืžืขืŸ ืขืคึผืขืก ืงืึธืžืคึผืœื™ืฆื™ืจื˜: ืคึฟืึทืจ ื“ืขื ืขืก ืื™ื– ื™ื ื•ื•ืึธื•ืง-SqlCmd. ืึธื‘ืขืจ ื“ื™ ืžืึทื ืฉืึทืคึฟื˜ ืื•ื™ืš ื”ืื˜ ื–ื™ื™ืŸ ืื™ื™ื’ืขื ืข ื“ื–ืฉืึธื•ืงืก:

  • ืื•ื™ื‘ ืื™ืจ ืึทืจื™ื‘ืขืจืคื™ืจืŸ ืึท ื˜ืขืงืข ืฆื• ืื™ืจ ื“ื•ืจืš - ื™ื ืคึผื•ื˜ ื˜ืขืงืข, ื“ืขืžืืœื˜ ืึทืจื•ื™ืกื’ืึทื ื’ ื ื™ื˜ ื“ืืจืฃ, ื“ืขืจืฆื•, ืขืก ื˜ืจืื’ื˜ ืึท ืกื™ื ื˜ืึทืงืก ื˜ืขื•ืช
  • -OutputFile ื ื™ื™ืŸ, ื“ืขืจ ื‘ืึทืคึฟืขืœ ืงืขืจื˜ ืื™ืจ ื“ื™ ืจืขื–ื•ืœื˜ืึทื˜ ื•ื•ื™ ืึท ื›ื™ื™ืคืขืฅ
  • ืขืก ื–ืขื ืขืŸ ืฆื•ื•ื™ื™ ืกื™ื ื˜ืึทืงืกื™ื– ืคึฟืึทืจ ืกืคึผืขืฆื™ืคื™ืฆื™ืจืŸ ืึท ืกืขืจื•ื•ืขืจ: -ServerInstance -Username -Password -Database ืื•ืŸ ื“ื•ืจืš -ConnectionString. ืึทื“ืœื™ ื’ืขื ื•ื’, ืื™ืŸ ื“ืขืจ ืขืจืฉื˜ืขืจ ืคืึทืœ ืขืก ืื™ื– ื ื™ื˜ ืžืขื’ืœืขืš ืฆื• ืกืคึผืขืฆื™ืคื™ืฆื™ืจืŸ ืึท ืคึผืึธืจื˜ ืื ื“ืขืจืข ื•ื•ื™ 1433.
  • ื˜ืขืงืกื˜ ืจืขื–ื•ืœื˜ืึทื˜, ื˜ื™ืคึผ PRINT, ื•ื•ืึธืก ืื™ื– ืคืฉื•ื˜ "ื’ืขื›ืืคื˜" sqlcmd, ืคึฟืึทืจ ื™ื ื•ื•ืึธื•ืง-SqlCmd ืื™ื– ืึท ืคึผืจืึธื‘ืœืขื
  • ืื•ืŸ ื“ืขืจ ืขื™ืงืจ: ืจื•ื‘ึฟ ืžืกืชึผืžื ื“ื™ื™ืŸ ืœื™ื ื•ืงืก ื˜ื•ื˜ ื ื™ืฉื˜ ื”ืึธื‘ืŸ ื“ืขื ืงืžื“ืœืขื˜!

ืื•ืŸ ื“ืึธืก ืื™ื– ื“ืขืจ ื”ื•ื™ืคึผื˜ ืคึผืจืึธื‘ืœืขื. ื‘ืœื•ื™ื– ืื™ืŸ ืžืึทืจืฅ ื“ืขื ืงืžื“ืœืขื˜ ื’ืขื•ื•ืืจืŸ ื‘ื ื™ืžืฆื ืคึฟืึทืจ ื ื™ื˜-ื•ื•ื™ื ื“ืึธื•ื– ืคึผืœืึทื˜ืคืึธืจืžืก, ืื•ืŸ ืœืขืกืึธืฃ ืžื™ืจ ืงืขื ืขืŸ ืคืึธืจื•ื™ืก!

ื•ื•ืขืจื™ืึทื‘ืึทืœ ืกืึทื‘ืกื˜ื™ื˜ื•ืฉืึทืŸ

sqlcmd ื”ืื˜ ื‘ื™ื™ึทื˜ืขื•ื•ื“ื™ืง ืกืึทื‘ืกื˜ื™ื˜ื•ืฉืึทืŸ ื ื™ืฆืŸ -v, ืคึฟืึทืจ ื‘ื™ื™ึทืฉืคึผื™ืœ ื•ื•ื™ ื“ืึธืก:

# $conn ัะพะดะตั€ะถะธั‚ ะฝะฐั‡ะฐะปะพ ะบะพะผะฐะฝะดั‹ sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res 
  -v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmd

ืื™ืŸ ื“ื™ SQL ืฉืจื™ืคื˜ ืžื™ืจ ื ื•ืฆืŸ ืกืึทื‘ืกื˜ื™ื˜ื™ื•ืฉืึทื ื–:

set @spid=$(spid)
set @age=$(age)

ืึทื–ื•ื™ ื“ืึธ ืขืก ืื™ื–. ืื™ืŸ * ื ื™ืงืก ื•ื•ืขืจื™ืึทื‘ืึทืœ ืกืึทื‘ืกื˜ื™ื˜ื™ื•ืฉืึทื ื– ื˜ืึธืŸ ื ื™ื˜ ืึทืจื‘ืขื˜. ืคึผืึทืจืึทืžืขื˜ืขืจ -v ืื™ื’ื ืึธืจื™ืจื˜. ื™ื• ื™ื ื•ื•ืึธื•ืง-SqlCmd ืื™ื’ื ืึธืจื™ืจื˜ - ื•ื•ืขืจื™ืึทื‘ืึทืœื–. ื›ืึธื˜ืฉ ื“ืขืจ ืคึผืึทืจืึทืžืขื˜ืขืจ ื•ื•ืึธืก ืกืคึผืขืฆื™ืคื™ืฆื™ืจื˜ ื“ื™ ื•ื•ืขืจื™ืึทื‘ืึทืœื– ื–ื™ืš ืื™ื– ืื™ื’ื ืึธืจื™ืจื˜, ื“ื™ ืกืึทื‘ืกื˜ื™ื˜ื™ื•ืฉืึทื ื– ื–ื™ืš ืึทืจื‘ืขื˜ - ืื™ืจ ืงืขื ืขืŸ ื ื•ืฆืŸ ืงื™ื™ืŸ ื•ื•ืขืจื™ืึทื‘ืึทืœื– ืคื•ืŸ ืฉืขืœ. ืึธื‘ืขืจ, ืื™ืš ืื™ื– ื’ืขื•ื•ืขืŸ ื‘ืืœื™ื™ื“ื™ืงื˜ืขืจ ืคื•ืŸ ื“ื™ ื•ื•ืขืจื™ืึทื‘ืึทืœื– ืื•ืŸ ื‘ืึทืฉืœืึธืกืŸ ื ื™ืฉื˜ ืฆื• ืึธืคืขื ื’ืขืŸ ืื•ื™ืฃ ื–ื™ื™ ื‘ื™ื™ึท ืึทืœืข, ืื•ืŸ ืึทืงื˜ืึทื“ ื’ืจืึธื‘ ืื•ืŸ ืคึผืจื™ืžื™ื˜ื™ื•ื•ืœื™, ื–ื™ื ื˜ ื“ื™ SQL ืกืงืจื™ืคึผืก ื–ืขื ืขืŸ ืงื•ืจืฅ:

# 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"

ื“ืึธืก, ื•ื•ื™ ืื™ืจ ืคึฟืึทืจืฉื˜ื™ื™ืŸ, ืื™ื– ืฉื•ื™ืŸ ืึท ืคึผืจืึธื‘ืข ืคึฟื•ืŸ ื“ื™ ื™ื•ื ื™ืงืก ื•ื•ืขืจืกื™ืข.

ื•ืคึผืœืึธืึทื“ื™ื ื’ ื˜ืขืงืขืก

ืื™ืŸ ื“ื™ Windows ื•ื•ืขืจืกื™ืข, ืงื™ื™ืŸ ืึธืคึผืขืจืึทืฆื™ืข ืื™ื– ื’ืขื•ื•ืขืŸ ื‘ืื’ืœื™ื™ื˜ ื“ื•ืจืš ืึท ืงืึธื ื˜ืจืึธืœื™ืจืŸ: ืžื™ืจ ืœื•ื™ืคืŸ sqlcmd, ื‘ืืงื•ืžืขืŸ ืขื˜ืœืขื›ืข ืžื™ืŸ ืคื•ืŸ ื–ื™ื“ืœืขืŸ ืื™ืŸ ื“ืขืจ ืจืขื–ื•ืœื˜ืึทื˜ ื˜ืขืงืข, ืึทื˜ืึทื˜ืฉื˜ ื“ืขื ื˜ืขืงืข ืฆื• ื“ื™ ืงืึธื ื˜ืจืึธืœื™ืจืŸ ื˜ืขืœืขืจ. ืฆื•ืž ื’ืœื™ืง, SQL ืกืขืจื•ื•ืขืจ ื’ืขืืจื‘ืขื˜ ืื•ื™ืฃ ื“ืขืจ ื–ืขืœื‘ื™ืงืขืจ ืกืขืจื•ื•ืขืจ ื•ื•ื™ Jenkins, ืขืก ืื™ื– ื’ืขื•ื•ืขืŸ ื’ืขื˜ืืŸ ืขืคึผืขืก ื•ื•ื™ ื“ืึธืก:

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

ืื–ื•ื™, ืžื™ืจ ืฉืœื™ื ื’ืขืŸ ื“ื™ BCP ื˜ืขืงืข ืœืขื’ืึทืžืจืข ืื•ืŸ ืฉื˜ื•ืคึผืŸ ืขืก ืื™ืŸ ื“ื™ nvarchar (ืžืึทืงืก) ืคืขืœื“ ืคื•ืŸ ื“ื™ ืงืึธื ื˜ืจืึธืœื™ืจืŸ ื˜ื™ืฉ. ืคืืจืฉื˜ื™ื™ื˜ ื–ื™ืš ืื– ื“ื™ ื’ืื ืฆืข ืกื™ืกื˜ืขื ืื™ื– ืฆืขืคืืœืŸ, ื•ื•ื™ื™ืœ ืื ืฉื˜ืื˜ ื ืกืงืœ ืกืขืจื•ื•ืขืจ ื”ืื‘ ืื™ืš ื‘ืืงื•ืžืขืŸ RDS, ืื•ืŸ BULK INSERT ืืจื‘ืขื˜ ื‘ื›ืœืœ ื ื™ืฉื˜ ื“ื•ืจืš UNC ืฆื•ืœื™ื‘ ื ืคืจื•ืื•ื• ืฆื• ื ืขืžืขืŸ ืืŸ ืขืงืกืงืœื•ืกื™ื•ื•ืข ืฉืœืืก ืื•ื™ืฃ ื ืคื™ื™ืœ, ืื•ืŸ ื‘ื™ื™ RDS ืื™ื– ื“ืืก ื‘ื›ืœืœ ื“ื•ืžื ืคื•ืŸ ื“ื™ ืกืึทืžืข ืึธื ื”ื™ื™ื‘. ืึทื–ื•ื™ ืื™ืš ื‘ืึทืฉืœืึธืกืŸ ืฆื• ื˜ื•ื™ืฉืŸ ื“ื™ ืกื™ืกื˜ืขื ืคึผืœืึทืŸ, ืกื˜ืึธืจื™ื ื’ ื“ื™ ืงืึธื ื˜ืจืึธืœื™ืจืŸ ืฉื•ืจื” ื“ื•ืจืš ืฉื•ืจื”:

CREATE TABLE AuditOut (
  ID int NULL,
  TextLine nvarchar(max) NULL,
  n int IDENTITY(1,1) PRIMARY KEY
  )

ืื•ืŸ ืฉืจื™ื™ื‘ ืึทื–ื•ื™ ืื™ืŸ ื“ืขื ื˜ื™ืฉ:

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() 
  }  

ืฆื• ืื•ื™ืกืงืœื™ื™ึทื‘ืŸ ืื™ื ื”ืึทืœื˜, ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ืื•ื™ืกืงืœื™ื™ึทื‘ืŸ ื“ื•ืจืš ืฉื™ื™ึทืŸ, ื˜ืฉื•ื–ื™ื ื’ ืื™ืŸ ืกื“ืจ n (ืื™ื“ืขื ื˜ื™ื˜ืขื˜).

ืื™ืŸ ื“ืขืจ ื•ื•ื™ื™ึทื˜ืขืจ ืึทืจื˜ื™ืงืœ ืื™ืš ื•ื•ืขื˜ ื’ื™ื™ืŸ ืื™ืŸ ืžืขืจ ื“ืขื˜ืึทืœ ื•ื•ืขื’ืŸ ื•ื•ื™ ื“ืึธืก ืึทืœืข ื™ื ื˜ืขืจืึทืงืฅ ืžื™ื˜ Jenkins.

ืžืงื•ืจ: www.habr.com

ืœื™ื™ื’ืŸ ืึท ื‘ืึทืžืขืจืงื•ื ื’