diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3e925ea68..295a47844 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -55,6 +55,12 @@ RUN apt-get install -y wget apt-transport-https software-properties-common && \ RUN pwsh -Command "Install-Module -Name SqlServer -Scope AllUsers -AllowClobber -Force" +RUN pwsh -Command "Install-Module -Name Az -AllowClobber -Scope AllUsers -Force" +RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash +RUN curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64 \ + && chmod +x ./bicep \ + && mv ./bicep /usr/local/bin/bicep + # Set the default shell to PowerShell SHELL ["pwsh", "-Command"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6a3f6915e..5f4b1512b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -51,6 +51,7 @@ "github.vscode-github-actions", "ms-dotnettools.csdevkit", "ms-vscode.powershell", + "ms-azuretools.vscode-bicep", ] } }, diff --git a/.github/workflows/aks_build_and_test.yml b/.github/workflows/aks_build_and_test.yml index a52e93491..99be52e65 100644 --- a/.github/workflows/aks_build_and_test.yml +++ b/.github/workflows/aks_build_and_test.yml @@ -243,7 +243,7 @@ jobs: Write-Host ('::set-output name=osType::'+$osType); - name: download tSQLtAndTests artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: tSQLtAndTests path: "${{ github.workspace }}\\tSQLt\\Build_Artifact" diff --git a/.github/workflows/build_and_test_on_spawn.yml b/.github/workflows/build_and_test_on_spawn.yml index 406af07b5..c87d6fa45 100644 --- a/.github/workflows/build_and_test_on_spawn.yml +++ b/.github/workflows/build_and_test_on_spawn.yml @@ -184,7 +184,7 @@ jobs: Write-Host "✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ ✨ "; - name: download tSQLtBuildArtifact artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: tSQLtBuildArtifact path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLtBuild" @@ -226,13 +226,13 @@ jobs: path: tSQLt - name: download tSQLtBuildArtifact artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: tSQLtBuildArtifact path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLtBuild" - name: download tSQLt dacpac artifact(s) - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: dacpacs path: "${{ github.workspace }}\\tSQLt\\Build\\output\\DacpacBuild" @@ -290,13 +290,13 @@ jobs: path: tSQLt - name: download public tSQLt artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: tSQLtPublic path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLt\\public" - name: download validation tSQLt artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: tSQLtValidation path: "${{ github.workspace }}\\tSQLt\\Build\\output\\tSQLt\\validation" diff --git a/Build/CommonFunctionsAndMethods.psm1 b/Build/CommonFunctionsAndMethods.psm1 index 2b8e04603..63f51d5ee 100644 --- a/Build/CommonFunctionsAndMethods.psm1 +++ b/Build/CommonFunctionsAndMethods.psm1 @@ -1,7 +1,7 @@ -$__=$__ #quiesce warnings +$__=$__ #quiesce warnings $CommonFunctionsAndMethodsDir = $PSScriptRoot -Write-Host "Loading CommonFunctionsAndMethods.psm1 from: $PSCommandPath" - +Write-Verbose "Loading CommonFunctionsAndMethods.psm1 from: $PSCommandPath" +. (Join-Path $CommonFunctionsAndMethodsDir 'SQLServerConnection.ps1'); $MergeHashTables = {param([HashTable]$base,[HashTable]$new);$new.GetEnumerator()|%{$base.remove($_.Key);$base += @{$_.Key=$_.Value}};$base;}; $AddTagsToResourceGroup = @@ -15,96 +15,7 @@ $SQLPrintCurrentTime = "EXEC('DECLARE @C VARCHAR(MAX)=CONVERT(VARCHAR(MAX),SYSUT Function Log-Output{[cmdletbinding()]Param([parameter(ValueFromPipeline)]$I);Process{Write-Host ([string]::Concat($GetUTCTimeStamp.Invoke()[0],[string]::Concat(" $I")));};}; -if (-not ([System.Management.Automation.PSTypeName]'SqlServerConnection').Type) { - - class SqlServerConnection { - hidden [string]$ServerName - hidden [string]$UserName - hidden [System.Security.SecureString]$Password - hidden [bool]$TrustedConnection - hidden [string]$ApplicationName - hidden [string]$BaseConnectionString = "Connect Timeout=60;TrustServerCertificate=true;" - - SqlServerConnection([string]$ServerName, [string]$UserName, [System.Security.SecureString]$Password,[string]$ApplicationName) { - $this.ServerName = $ServerName.Trim() - $this.UserName = $UserName.Trim() - $this.Password = $Password - $this.TrustedConnection = false - $this.ApplicationName = $ApplicationName.Trim() - } - SqlServerConnection([string]$ServerName,[string]$ApplicationName) { - $this.ServerName = $ServerName.Trim() - $this.UserName = $null - $this.Password = $null - $this.TrustedConnection = true - $this.ApplicationName = $ApplicationName.Trim() - } - - [string] ToString() { - - - return @{ - ServerName= $($this.ServerName) - UserName= $($this.UserName) - Password= (ConvertFrom-SecureString $this.Password -AsPlainText) - TrustedConnection= $($this.TrustedConnection) - ApplicationName= $($this.ApplicationName) - BaseConnectionString= $($this.BaseConnectionString) - }| ConvertTo-Json -Compress - } - - static [string] ToStringStatic([SqlServerConnection]$instance) { - if ($null -eq $instance) { - return "$null" - } - else { - return $instance.ToString() - } - } - - [string] GetServerName() { - return $this.ServerName - } - - [string] GetUserName() { - return $this.UserName - } - - [System.Security.SecureString] GetPassword() { - return $this.Password - } - - [bool] GetTrustedConnection() { - return $this.TrustedConnection - } - - hidden [string] EscapeAndQuoteConnectionStringValue([string]$value) { - return "`"$( $value.Replace('"', '""') )`"" - } - - [string] GetConnectionString([string]$DatabaseName = '', [string]$ApplicationNameSuffix = '') { - $connectionString = "Server=$($this.EscapeAndQuoteConnectionStringValue($this.ServerName));" - - if ($this.TrustedConnection) { - $connectionString += "Integrated Security=SSPI;" - } else { - $connectionString += "User Id=$($this.EscapeAndQuoteConnectionStringValue($this.UserName));" - $connectionString += "Password=$($this.EscapeAndQuoteConnectionStringValue((ConvertFrom-SecureString $this.Password -AsPlainText)));" - } - if(![string]::IsNullOrWhiteSpace($DatabaseName)){ - $connectionString += "Initial Catalog=$($this.EscapeAndQuoteConnectionStringValue($DatabaseName));" - } - $PApplicationName = $this.ApplicationName; - if(![string]::IsNullOrWhiteSpace($ApplicationNameSuffix)){ - $PApplicationName+=".$ApplicationNameSuffix" - } - $connectionString += "Application Name=$($this.EscapeAndQuoteConnectionStringValue($PApplicationName));" - return $this.BaseConnectionString+$connectionString - } - } -} - -Function Exec-SqlFile +Function Invoke-SqlFile { [CmdletBinding()] param( @@ -129,10 +40,10 @@ Function Exec-SqlFile $parameters['Verbose'] = $true } - $dddbefore = Get-Date;Write-Warning("------->>BEFORE<<-------(CommonFunctionsAndMethods.p1:Exec-SqlFile:Invoke-SqlCommand[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(CommonFunctionsAndMethods.p1:Invoke-SqlFile:Invoke-SqlCommand[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") $results = (Invoke-SqlCmd @parameters) - $dddafter = Get-Date;Write-Warning("------->>After<<-------(CommonFunctionsAndMethods.p1:Exec-SqlFile:Invoke-SqlCommand[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") - Write-Warning("Runtime in Milliseconds: $(($dddafter-$dddbefore).TotalMilliseconds)") + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(CommonFunctionsAndMethods.p1:Invoke-SqlFile:Invoke-SqlCommand[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Runtime in Milliseconds: $(($dddafter-$dddbefore).TotalMilliseconds)") return $results } @@ -189,11 +100,11 @@ Function Invoke-SQLFileOrQuery AdditionalParameters = $AdditionalParameters PrintSqlOutput = $PrintSqlOutput } -$dddbefore = Get-Date;Write-Warning("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Exec-SqlFile[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") - $QueryOutput = Exec-SqlFile @parameters -$dddafter = Get-Date;Write-Warning("------->>After<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Exec-SqlFile[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") -$dddafter-$dddbefore - return $QueryOutput +$dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Invoke-SqlFile[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $QueryOutput = Invoke-SqlFile @parameters +$dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-SQLFileOrQuery:Invoke-SqlFile[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") +Write-Verbose("Runtime in Milliseconds: $(($dddafter-$dddbefore).TotalMilliseconds)") + return $QueryOutput } catch{ throw @@ -205,48 +116,6 @@ $dddafter-$dddbefore } } -Function Get-SqlConnectionString -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ServerName, - [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Login, - [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $DatabaseName - ); - - $ServerNameTrimmed = $ServerName.Trim(); - $LoginTrimmed = $Login.Trim(); - - <# - ☹️ - This is so questionable, but it looks like sqlpackage cannot handle valid connection strings that use a valid server alias. - The following snippet is meant to spelunk through the registry and extract the actual server from the alias. - ☹️ - #> - $resolvedServerName = $ServerNameTrimmed; - $serverAlias = Get-Item -Path HKLM:\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo -ErrorAction SilentlyContinue; - if ($null -ne $serverAlias -And $serverAlias.GetValueNames() -contains $ServerNameTrimmed) { - $aliasValue = $serverAlias.GetValue($ServerNameTrimmed) - if ($aliasValue -match "DBMSSOCN[,](.*)"){ - $resolvedServerName = $Matches[1]; - } - } - - <# When using Windows Authentication, you must use "Integrated Security=SSPI" in the SqlConnectionString. Else use "User ID=;Password=;" #> - if ($LoginTrimmed -match '((.*[-][uU])|(.*[-][pP]))+.*'){ - $AuthenticationString = $LoginTrimmed -replace '^((\s*[-][uU]\s+(?\S+)\s*)|(\s*[-][pP]\s+)((?[''"])(?.*?)\k|(?\S+))\s*)+$', 'User Id=${user};Password="${password}"' - } - elseif ($LoginTrimmed -eq "-E"){ - $AuthenticationString = "Integrated Security=SSPI;"; - } - else{ - throw $LoginTrimmed + " is not supported here." - } - - $SqlConnectionString = "Data Source="+$resolvedServerName+";"+$AuthenticationString+";Connect Timeout=60;Initial Catalog="+$DatabaseName+";TrustServerCertificate=true;"; - $SqlConnectionString; -} - function Get-TempFileForQuery { [CmdletBinding()] [OutputType([string])] @@ -266,7 +135,7 @@ function Get-FriendlySQLServerVersion { [Parameter(Mandatory=$false)][switch]$Quiet ) $GetFriendlySQLServerVersionFullPath = (Get-ChildItem -Path ($PSScriptRoot + '/output/*') -include "GetFriendlySQLServerVersion.sql" -Recurse | Select-Object -First 1 ).FullName; - $resultSet = (Exec-SqlFile -SqlServerConnection $SqlServerConnection -FileNames @($GetFriendlySQLServerVersionFullPath) -DatabaseName 'tempdb'); + $resultSet = (Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames @($GetFriendlySQLServerVersionFullPath) -DatabaseName 'tempdb'); $FriendlyVersion = ($resultSet.FriendlyVersion) if(!$Quiet){Log-Output "Friendly SQL Server Version: $FriendlyVersion"}; return $FriendlyVersion @@ -317,8 +186,8 @@ Function Remove-ResourceGroup{ [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroupName, [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $BuildId); - Write-Output "▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-"; - Write-Output ("[{0}]Start processing delete for {1}" -f ((get-date).toString("O")), ($ResourceGroupName)); + Write-Verbose "▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-"; + Write-Verbose ("[{0}]Start processing delete for {1}" -f ((get-date).toString("O")), ($ResourceGroupName)); $MyAzResourceGroup = (Get-AzResourceGroup -name "$ResourceGroupName"); if(("RemovalBy" -in $MyAzResourceGroup.tags.keys) -and (![string]::isnullorempty($MyAzResourceGroup.tags.RemovalBy))) { @@ -326,37 +195,37 @@ Function Remove-ResourceGroup{ } if($null -ne $MyAzResourceGroup) { $Tags = @{}; - Write-Output ("Add Tag to {0}" -f $ResourceGroupName); + Write-Verbose ("Add Tag to {0}" -f $ResourceGroupName); $Tags = $MyAzResourceGroup.Tags; $Tags.remove("RemovalBy"); $Tags += @{"RemovalBy"="$BuildId"}; $MyAzResourceGroup | Set-AzResourceGroup -Tags $Tags; Start-Sleep 10; - Write-Output ("Confirming Tags are still in place for {0}" -f $ResourceGroupName); + Write-Verbose ("Confirming Tags are still in place for {0}" -f $ResourceGroupName); $MyAzResourceGroup = $MyAzResourceGroup | Get-AZResourceGroup | Where-Object {$_.Tags.RemovalBy -eq "$BuildId"}; $MyAzResourceGroup.Tags | Format-Table; if($null -ne $MyAzResourceGroup) { - Write-Output "Removing Locks" + Write-Verbose "Removing Locks" $retrievedResourceGroupName = $MyAzResourceGroup.ResourceGroupName; Get-AzResource -ResourceGroupName $retrievedResourceGroupName | ForEach-Object { Get-AzResourceLock -ResourceType $_.ResourceType -ResourceName $_.Name -ResourceGroupName $_.ResourceGroupName | ForEach-Object{ - Write-Output ("{0} -> {1}" -f $_.ResourceType, $_.ResourceName); + Write-Verbose ("{0} -> {1}" -f $_.ResourceType, $_.ResourceName); $_ | Remove-AzResourceLock -Force } } - Write-Output ("Removing RG {0}" -f $retrievedResourceGroupName); + Write-Verbose ("Removing RG {0}" -f $retrievedResourceGroupName); $MyAzResourceGroup | Remove-AzResourceGroup -Force; } else { - Write-Output ("Tags changed by another process. Resource Group {0} is no longer eligible to be deleted." -f $ResourceGroupName); + Write-Verbose ("Tags changed by another process. Resource Group {0} is no longer eligible to be deleted." -f $ResourceGroupName); } } else { - Write-Output ("Processing skipped for Resource Group: {0} Build Id: {1}" -f $ResourceGroupName, $BuildId); + Write-Verbose ("Processing skipped for Resource Group: {0} Build Id: {1}" -f $ResourceGroupName, $BuildId); } - Write-Output ("[{0}]Done processing delete for {1}" -f ((get-date).toString("O")), ($ResourceGroupName)) - Write-Output "▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-"; + Write-Verbose ("[{0}]Done processing delete for {1}" -f ((get-date).toString("O")), ($ResourceGroupName)) + Write-Verbose "▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-▀-▄-_-▄-"; } Function Get-SnipContent { @@ -398,7 +267,7 @@ Function Replace-InFile { $isRegex = $true $rv = $rv[0] } - Write-Host("Replacing >$_< with >$rv<..."); + Write-Verbose("Replacing >$_< with >$rv<..."); if($isRegex){ $fileContent = $fileContent -replace $_, $rv }else{ @@ -412,6 +281,6 @@ Function Replace-InFile { } Log-Output($GetUTCTimeStamp.Invoke(),"Done: Loading CommonFunctionsAndMethods"); -Export-ModuleMember -Function Log-Output, Exec-SqlFile, Get-SqlConnectionString, Get-TempFileForQuery, Get-FriendlySQLServerVersion, Update-Archive +Export-ModuleMember -Function Log-Output, Invoke-SqlFile, Get-TempFileForQuery, Get-FriendlySQLServerVersion, Update-Archive Export-ModuleMember -Function Remove-ResourceGroup, Get-SnipContent, Replace-InFile, Invoke-SQLFileOrQuery, Remove-DirectoryQuietly Export-ModuleMember -Variable $CommonFunctionsAndMethodsDir, $SQLPrintCurrentTime diff --git a/Build/LocalBuild.ps1 b/Build/LocalBuild.ps1 index 906338593..e8bd67647 100644 --- a/Build/LocalBuild.ps1 +++ b/Build/LocalBuild.ps1 @@ -1,18 +1,26 @@ -using module "./CommonFunctionsAndMethods.psm1"; - +[CmdletBinding()] param( [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $ServerName = 'localhost,1433', [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][string] $UserName = "sa" , [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][securestring] $Password = (ConvertTo-SecureString "P@ssw0rd" -AsPlainText), [Parameter(Mandatory=$true, ParameterSetName = 'TrustedCon')][ValidateNotNullOrEmpty()][switch] $TrustedConnection, [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $DatabaseName = 'tSQLt.TmpBuild.DacPacBuild', + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $pfxFilePath = (Join-Path $env:TSQLTCERTPATH "tSQLtOfficialSigningKey.pfx" |Resolve-Path), + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][securestring] $pfxPassword = (ConvertTo-SecureString -String "$env:TSQLTCERTPASSWORD" -Force -AsPlainText), [Parameter(Mandatory=$false)][switch]$KeepTemp, [Parameter(Mandatory=$false, ParameterSetName="IgnoreMe")][string]$IgnoreMe ) $PSDefaultParameterValues = $PSDefaultParameterValues.clone() $PSDefaultParameterValues += @{'*:ErrorAction' = 'Stop'} -Get-Module -Name CommonFunctionsAndMethods | Select-Object Name, Path, Version +Write-Verbose "Starting execution of LocalBuild.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path $invocationDir "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Verbose "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +. (Join-Path $invocationDir 'SQLServerConnection.ps1'); function CleanTemp { param ( @@ -29,20 +37,20 @@ $invocationDir = $PSScriptRoot Push-Location -Path $invocationDir try{ - if($TrustedConnection){ - Write-Warning('GH:TC') - $SqlServerConnection = [SqlServerConnection]::new($ServerName,"LocalBuild"); - }else{ - Write-Warning('GH:UP') - $SqlServerConnection = [SqlServerConnection]::new($ServerName,$UserName,$Password,"LocalBuild"); - } - Log-Output(''); Log-Output("+--------------------------------------------------------------------+"); Log-Output("| ***** Executing Local tSQLt Build ***** |"); Log-Output("+--------------------------------------------------------------------+"); Log-Output(''); + if($TrustedConnection){ + Log-Output('Selecting Trusted Connection') + $SqlServerConnection = [SqlServerConnection]::new($ServerName,"LocalBuild"); + }else{ + Log-Output('Selecting UserName/Password Connection') + $SqlServerConnection = [SqlServerConnection]::new($ServerName,$UserName,$Password,"LocalBuild"); + } + Log-Output('+ - - - - - - - - - - - - - - - - - +') Log-Output(': Cleaning Environment :') @@ -57,7 +65,7 @@ try{ Log-Output(': Starting CLR Build :') Log-Output('+ - - - - - - - - - - - - - - - - - +') - & ./tSQLt_BuildCLR.ps1 + & ./tSQLt_BuildCLR.ps1 -pfxFilePath $pfxFilePath -pfxPassword $pfxPassword CleanTemp $KeepTemp; Log-Output('+ - - - - - - - - - - - - - - - - - +') diff --git a/Build/LocalValidate.ps1 b/Build/LocalValidate.ps1 index d423be009..dd14882a0 100644 --- a/Build/LocalValidate.ps1 +++ b/Build/LocalValidate.ps1 @@ -1,5 +1,4 @@ -using module "./CommonFunctionsAndMethods.psm1"; - +[CmdletBinding()] param( [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $ServerName = 'localhost,1433', [Parameter(Mandatory=$true, ParameterSetName = 'UserPass')][ValidateNotNullOrEmpty()][string] $UserName = "sa" , @@ -11,18 +10,23 @@ param( $PSDefaultParameterValues = $PSDefaultParameterValues.clone() $PSDefaultParameterValues += @{'*:ErrorAction' = 'Stop'} -Get-Module -Name CommonFunctionsAndMethods | Select-Object Name, Path, Version - - +Write-Verbose "Starting execution of LocalBuild.ps1" +$__=$__ #quiesce warnings $invocationDir = $PSScriptRoot Push-Location -Path $invocationDir +$cfam = (Join-Path $invocationDir "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Verbose "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +. (Join-Path $invocationDir 'SQLServerConnection.ps1'); + + try{ if($TrustedConnection){ - Write-Warning('GH:TC') + Log-Output('Selecting Trusted Connection') $SqlServerConnection = [SqlServerConnection]::new($ServerName,"LocalBuild"); }else{ - Write-Warning('GH:UP') + Log-Output('Selecting UserName/Password Connection') $SqlServerConnection = [SqlServerConnection]::new($ServerName,$UserName,$Password,"LocalBuild"); } diff --git a/Build/SQL/DisableExternalAccess.sql b/Build/SQL/DisableExternalAccess.sql new file mode 100644 index 000000000..4f05b11b2 --- /dev/null +++ b/Build/SQL/DisableExternalAccess.sql @@ -0,0 +1,4 @@ +IF(EXISTS(SELECT 1 FROM tSQLt.Info() WHERE HostPlatform NOT IN ('Linux'))) +BEGIN + EXEC tSQLt.EnableExternalAccess @try = 0, @enable=0; +END; diff --git a/Build/SQL/EnableExternalAccess.sql b/Build/SQL/EnableExternalAccess.sql index e1e8b36e3..2b1223f7f 100644 --- a/Build/SQL/EnableExternalAccess.sql +++ b/Build/SQL/EnableExternalAccess.sql @@ -1 +1,4 @@ -EXEC tSQLt.EnableExternalAccess @try = 1; +IF(EXISTS(SELECT 1 FROM tSQLt.Info() WHERE HostPlatform NOT IN ('Linux'))) +BEGIN + EXEC tSQLt.EnableExternalAccess @try = 0, @enable=1; +END; diff --git a/Build/SQL/ExecuteAsCleanup.sql b/Build/SQL/ExecuteAsCleanup.sql index 6c1622ae6..c98e6cfe2 100644 --- a/Build/SQL/ExecuteAsCleanup.sql +++ b/Build/SQL/ExecuteAsCleanup.sql @@ -13,6 +13,6 @@ END IF @Counter >= @MaxAttempts BEGIN - RAISERROR('Impersonation could not be reverted after %d attempts.', 16, 1, @MaxAttempts); + RAISERROR('WARNING: Impersonation could not be reverted after %d attempts.', 0, 1, @MaxAttempts) WITH NOWAIT; END GO \ No newline at end of file diff --git a/Build/SQLServerConnection.ps1 b/Build/SQLServerConnection.ps1 new file mode 100644 index 000000000..d65240772 --- /dev/null +++ b/Build/SQLServerConnection.ps1 @@ -0,0 +1,89 @@ +if (-not ([System.Management.Automation.PSTypeName]'SqlServerConnection').Type) { + + class SqlServerConnection { + hidden [string]$ServerName + hidden [string]$UserName + hidden [System.Security.SecureString]$Password + hidden [bool]$TrustedConnection + hidden [string]$ApplicationName + hidden [string]$BaseConnectionString = "Connect Timeout=60;TrustServerCertificate=true;" + + SqlServerConnection([string]$ServerName, [string]$UserName, [System.Security.SecureString]$Password,[string]$ApplicationName) { + $this.ServerName = $ServerName.Trim() + $this.UserName = $UserName.Trim() + $this.Password = $Password + $this.TrustedConnection = false + $this.ApplicationName = $ApplicationName.Trim() + } + # SqlServerConnection([string]$ServerName,[string]$ApplicationName) { + # $this.ServerName = $ServerName.Trim() + # $this.UserName = $null + # $this.Password = $null + # $this.TrustedConnection = true + # $this.ApplicationName = $ApplicationName.Trim() + # } + + [string] ToString() { + + + return @{ + ServerName= $($this.ServerName) + UserName= $($this.UserName) + Password= (ConvertFrom-SecureString $this.Password -AsPlainText) + TrustedConnection= $($this.TrustedConnection) + ApplicationName= $($this.ApplicationName) + BaseConnectionString= $($this.BaseConnectionString) + }| ConvertTo-Json -Compress + } + + static [string] ToStringStatic([SqlServerConnection]$instance) { + if ($null -eq $instance) { + return "$null" + } + else { + return $instance.ToString() + } + } + + [string] GetServerName() { + return $this.ServerName + } + + [string] GetUserName() { + return $this.UserName + } + + [System.Security.SecureString] GetPassword() { + return $this.Password + } + + [bool] GetTrustedConnection() { + return $this.TrustedConnection + } + + hidden [string] EscapeAndQuoteConnectionStringValue([string]$value) { + return "`"$( $value.Replace('"', '""') )`"" + } + + [string] GetConnectionString([string]$DatabaseName = '', [string]$ApplicationNameSuffix = '') { + $connectionString = "Server=$($this.EscapeAndQuoteConnectionStringValue($this.ServerName));" + + if ($this.TrustedConnection) { + $connectionString += "Integrated Security=SSPI;" + } else { + $connectionString += "User Id=$($this.EscapeAndQuoteConnectionStringValue($this.UserName));" + $connectionString += "Password=$($this.EscapeAndQuoteConnectionStringValue((ConvertFrom-SecureString $this.Password -AsPlainText)));" + } + if(![string]::IsNullOrWhiteSpace($DatabaseName)){ + $connectionString += "Initial Catalog=$($this.EscapeAndQuoteConnectionStringValue($DatabaseName));" + } + $PApplicationName = $this.ApplicationName; + if(![string]::IsNullOrWhiteSpace($ApplicationNameSuffix)){ + $PApplicationName+=".$ApplicationNameSuffix" + } + $connectionString += "Application Name=$($this.EscapeAndQuoteConnectionStringValue($PApplicationName));" + return $this.BaseConnectionString+$connectionString + } + } + } + \ No newline at end of file diff --git a/Build/tSQLt_Build.ps1 b/Build/tSQLt_Build.ps1 index a24a8ae32..061c973a1 100644 --- a/Build/tSQLt_Build.ps1 +++ b/Build/tSQLt_Build.ps1 @@ -122,13 +122,13 @@ try{ $toBeZipped = @("ReleaseNotes.txt", "License.txt", "tSQLt.class.sql", "Example.sql", "PrepareServer.sql"); $compress = @{ CompressionLevel = "Optimal" - DestinationPath = (Join-Path $outputPath "tSQLtFiles.zip") + DestinationPath = (Join-Path $OutputPath "tSQLtFiles.zip") } Get-ChildItem -Path (Join-Path $tempPath "*") -Include $toBeZipped | Compress-Archive @compress # $toBeCopied = @("Version.txt", "tSQLt.class.sql", "CommitId.txt", "GetFriendlySQLServerVersion.sql", "CreateBuildLog.sql"); $toBeCopied = @("Version.txt", "CommitId.txt", "tSQLt.Private_GetAssemblyKeyBytes.sql", "GetFriendlySQLServerVersion.sql", "CreateBuildLog.sql"); - $toBeCopied | ForEach-Object{(Join-Path $TempPath $_ | Resolve-Path )| Copy-Item -Destination $outputPath;} + $toBeCopied | ForEach-Object{(Join-Path $TempPath $_ | Resolve-Path )| Copy-Item -Destination $OutputPath;} # Copy-Item (Join-Path $tempPath "ReleaseNotes.txt" | Resolve-Path) -Destination (Join-Path $outputPath "ReadMe.txt"); Log-Output("Creating tSQLt Snippets...") diff --git a/Build/tSQLt_Build/ConcatenateFiles.ps1 b/Build/tSQLt_Build/ConcatenateFiles.ps1 index ac7fdf527..1375262b6 100644 --- a/Build/tSQLt_Build/ConcatenateFiles.ps1 +++ b/Build/tSQLt_Build/ConcatenateFiles.ps1 @@ -58,7 +58,7 @@ function Concatenate-Files { $output = @() foreach ($file in $fileIterator) { - Write-Host("-->$file") + Write-Verbose("-->$file") $fileContent = Get-FileContent -filePath $file -bracket $bracket -includeFromStart $includeFromStart -separator $separator $output += $fileContent } @@ -66,9 +66,9 @@ function Concatenate-Files { return $output } -Write-Host("OutputFile: $OutputFile") -Write-Host("SeparatorTemplate: >$SeparatorTemplate<") -Write-Host("Input: $InputPath") +Write-Verbose("OutputFile: $OutputFile") +Write-Verbose("SeparatorTemplate: >$SeparatorTemplate<") +Write-Verbose("Input: $InputPath") if([string]::IsNullOrWhiteSpace($SeparatorTemplate)){ if($null -eq $SeparatorContent){ @@ -78,9 +78,9 @@ if([string]::IsNullOrWhiteSpace($SeparatorTemplate)){ else{ $SeparatorContent = Get-Content $SeparatorTemplate -ErrorAction Stop } -Write-Host(">--Separator Template-->") -$SeparatorContent|%{Write-Host(">:$_")} -Write-Host("<--Separator Template--<") +Write-Verbose(">--Separator Template-->") +$SeparatorContent|%{Write-Verbose(">:$_")} +Write-Verbose("<--Separator Template--<") if($Bracket -eq ''){ $IncludeFromStart = $true; } @@ -88,20 +88,20 @@ if($Bracket -eq ''){ try{ if($null -eq $InputPath){ - Write-Host("scriptPath: ") + Write-Verbose("scriptPath: ") $fileIterator = @() } elseif($InputPath -is [System.Collections.IEnumerable]){ - Write-Host("scriptPath: /") + Write-Verbose("scriptPath: /") $fileIterator = $InputPath } elseif (Test-Path $InputPath -PathType Container) { - Write-Host("scriptPath: $InputPath") + Write-Verbose("scriptPath: $InputPath") $fileIterator = Get-ChildItem $InputPath -Filter $IncludePattern } else { $scriptPath = (Split-Path $InputPath) - Write-Host("scriptPath: $scriptPath") + Write-Verbose("scriptPath: $scriptPath") $fileList = Get-Content $InputPath -ErrorAction Stop $fileIterator = $fileList | ForEach-Object { Join-Path $scriptPath $_ | Resolve-Path} } @@ -111,7 +111,7 @@ try{ $sv = $_["s"] $rv=$_["r"]; $isRegex = $_.ContainsKey("isRegex") -and $_["isRegex"]; - Write-Host("Replacing >$sv< with >$rv< [regex:$isRegex]..."); + Write-Verbose("Replacing >$sv< with >$rv< [regex:$isRegex]..."); if($isRegex){ $concatenatedContent = $concatenatedContent -replace $sv, $rv }else{ diff --git a/Build/tSQLt_BuildCLR.ps1 b/Build/tSQLt_BuildCLR.ps1 index a1a70e927..7acc1733a 100644 --- a/Build/tSQLt_BuildCLR.ps1 +++ b/Build/tSQLt_BuildCLR.ps1 @@ -1,6 +1,10 @@ using module "./CommonFunctionsAndMethods.psm1"; +param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $pfxFilePath , + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][securestring] $pfxPassword +) Push-Location -Path $PSScriptRoot - +Write-Verbose((Get-Location).Path) try{ $OutputPath = "./output/tSQLtCLR/"; $TempPath = "./temp/tSQLtCLR/"; @@ -11,10 +15,11 @@ try{ Get-ChildItem -Path ("../tSQLtCLR/") -Recurse -Include bin, obj|Foreach-Object{Remove-DirectoryQuietly -Path $_} <# Init directories, capturing the return values in a variable so that they don't print. #> - $_ = New-Item -ItemType "directory" -Path $TempPath; - $_ = New-Item -ItemType "directory" -Path $OutputPath; + $__=$__; + $__ = New-Item -ItemType "directory" -Path $TempPath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; - ../tSQLtCLR/Build.ps1 + ../tSQLtCLR/Build.ps1 -pfxFilePath $pfxFilePath -pfxPassword $pfxPassword Get-ChildItem -Path ("../tSQLtCLR/*/bin") -Recurse -Include *.dll | Copy-Item -Destination $TempPath; diff --git a/Build/tSQLt_BuildDacpac.ps1 b/Build/tSQLt_BuildDacpac.ps1 index 73563c9ed..b23e6daa1 100644 --- a/Build/tSQLt_BuildDacpac.ps1 +++ b/Build/tSQLt_BuildDacpac.ps1 @@ -1,4 +1,4 @@ -using module "./CommonFunctionsAndMethods.psm1"; +# using module "./CommonFunctionsAndMethods.psm1"; Param( [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][SqlServerConnection] $SqlServerConnection, @@ -11,6 +11,11 @@ Technically this should be called by a matrixed job, so that dacpacs are built f $__=$__ #quiesce warnings $invocationDir = $PSScriptRoot Push-Location -Path $invocationDir +$cfam = (Join-Path $invocationDir "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Verbose "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force +Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + try{ $OutputPath = (Join-Path $invocationDir "/output/DacpacBuild/"); @@ -29,14 +34,14 @@ try{ Set-Location $TempPath; Log-Output('Building Database') Log-Output('-- Executing ResetValidationServer.sql') - Exec-SqlFile -SqlServerConnection $SqlServerConnection -FileNames @('ResetValidationServer.sql'); + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames @('ResetValidationServer.sql'); Log-Output('-- Executing PrepareServer.sql') - Exec-SqlFile -SqlServerConnection $SqlServerConnection -FileNames 'PrepareServer.sql'; + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames 'PrepareServer.sql'; Log-Output('-- Executing CreateBuildDb.sql') - Exec-SqlFile -SqlServerConnection $SqlServerConnection -FileNames "CreateBuildDb.sql" -Database "tempdb" -AdditionalParameters @{NewDbName=$DacPacDatabaseName} -PrintSqlOutput $true; + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames "CreateBuildDb.sql" -Database "tempdb" -AdditionalParameters @{NewDbName=$DacPacDatabaseName} -PrintSqlOutput $true; Log-Output('-- Executing tSQLt.class.sql') - Exec-SqlFile -SqlServerConnection $SqlServerConnection -FileNames "tSQLt.class.sql" -Database "$DacPacDatabaseName"; - Write-Host('Building DACPAC') + Invoke-SqlFile -SqlServerConnection $SqlServerConnection -FileNames "tSQLt.class.sql" -Database "$DacPacDatabaseName"; + Log-Output('Building DACPAC') $FriendlySQLServerVersion = Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection; $tSQLtDacpacFileName = "tSQLt."+$FriendlySQLServerVersion+".dacpac"; $tSQLtApplicationName = "tSQLt."+$FriendlySQLServerVersion; diff --git a/Build/tSQLt_BuildPackage.ps1 b/Build/tSQLt_BuildPackage.ps1 index 6ac41c56a..9f2392fab 100644 --- a/Build/tSQLt_BuildPackage.ps1 +++ b/Build/tSQLt_BuildPackage.ps1 @@ -5,6 +5,18 @@ $invocationDir = $PSScriptRoot Push-Location -Path $invocationDir try{ + $tSQLtBuildPath = (Join-Path $invocationDir '/output/tSQLtBuild/'|Resolve-Path); + $tSQLtTestsPath = (Join-Path $invocationDir '/output/tSQLtTests/'|Resolve-Path); + $DacpacBuildPath = (Join-Path $invocationDir '/output/DacpacBuild/'|Resolve-Path); + # Write-Warning "BuildPackage Inputs:" + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $tSQLtBuildPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $tSQLtTestsPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $DacpacBuildPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + $OutputPath = (Join-Path $invocationDir "/output/tSQLt/"); $TempPath = (Join-Path $invocationDir "/temp/tSQLt/"); @@ -40,24 +52,31 @@ try{ Log-Output("Copying source files...") $files = @( - "/output/tSQLtBuild/Version.txt", - "/output/tSQLtBuild/CommitId.txt", - "/output/tSQLtBuild/CreateBuildLog.sql", - "/output/tSQLtBuild/GetFriendlySQLServerVersion.sql", - "/output/tSQLtTests/tSQLt.tests.zip", - "/output/tSQLtBuild/tSQLtSnippets(SQLPrompt).zip", - "/output/tSQLtBuild/tSQLtFiles.zip" + "$($tSQLtBuildPath)Version.txt", + "$($tSQLtBuildPath)CommitId.txt", + "$($tSQLtBuildPath)CreateBuildLog.sql", + "$($tSQLtBuildPath)GetFriendlySQLServerVersion.sql", + "$($tSQLtTestsPath)tSQLt.tests.zip", + "$($tSQLtBuildPath)tSQLtSnippets(SQLPrompt).zip", + "$($tSQLtBuildPath)tSQLtFiles.zip" ); - $files|%{(Join-Path $invocationDir $_ | Resolve-Path) | Copy-Item -Destination $SourcePath} - Get-ChildItem (Join-Path $invocationDir "/output/DacpacBuild") | Copy-Item -Destination $DacpacSourcePath + $files | ForEach-Object{$_ | Copy-Item -Destination $SourcePath} + Get-ChildItem $DacpacBuildPath | Copy-Item -Destination $DacpacSourcePath <# Copy files to temp path #> Expand-Archive -Path (Join-Path $SourcePath "tSQLtFiles.zip" | Resolve-Path) -DestinationPath $PublicTempPath; # Get-ChildItem -Path ($dir + "/output/DacpacBuild/tSQLtFacade.*.dacpac") | Copy-Item -Destination $FacadeDacpacPath; - Get-ChildItem -Path ($DacpacSourcePath) -Filter 'tSQLt.*.dacpac' | Copy-Item -Destination $tSQLtDacpacPath; + Get-ChildItem -Path $DacpacSourcePath -Filter 'tSQLt.*.dacpac' | Copy-Item -Destination $tSQLtDacpacPath; Copy-Item (Join-Path $PublicTempPath "ReleaseNotes.txt" | Resolve-Path) -Destination (Join-Path $PublicTempPath "ReadMe.txt"); + # Write-Warning "BuildPackage Pre-Zip:" + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $PublicTempPath -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + # Get-ChildItem $ValidationOutputFiles -Recurse |FT; + # Write-Warning "----------------------------------------------------------------" + <# Create the tSQLt.zip in the public output path #> $compress = @{ CompressionLevel = "Optimal" diff --git a/Build/tSQLt_BuildTests.ps1 b/Build/tSQLt_BuildTests.ps1 index f52dd5514..d7dbbea51 100644 --- a/Build/tSQLt_BuildTests.ps1 +++ b/Build/tSQLt_BuildTests.ps1 @@ -14,10 +14,11 @@ try{ Remove-DirectoryQuietly -Path $TempPath; Remove-DirectoryQuietly -Path $OutputPath; <# Init directories, capturing the return values in a variable so that they don't print. #> - $_ = New-Item -ItemType "directory" -Path $TempPath; - $_ = New-Item -ItemType "directory" -Path $SourcePath; - $_ = New-Item -ItemType "directory" -Path $PackagePath; - $_ = New-Item -ItemType "directory" -Path $OutputPath; + $__ = $__ + $__ = New-Item -ItemType "directory" -Path $TempPath; + $__ = New-Item -ItemType "directory" -Path $SourcePath; + $__ = New-Item -ItemType "directory" -Path $PackagePath; + $__ = New-Item -ItemType "directory" -Path $OutputPath; Log-Output("Copying source files...") $files = @( @@ -139,6 +140,7 @@ try{ "Install(tSQLtAssemblyKey).sql", "ChangeDbAndExecuteStatement(tSQLt.Build).sql", "EnableExternalAccess.sql", + "DisableExternalAccess.sql", "Drop(master.tSQLt_testutil).sql", "Install(master.tSQLt_testutil).sql", "GetFailedTestCount.sql", diff --git a/Build/tSQLt_Validate.ps1 b/Build/tSQLt_Validate.ps1 index 28203f6d0..22ff1d02b 100644 --- a/Build/tSQLt_Validate.ps1 +++ b/Build/tSQLt_Validate.ps1 @@ -9,7 +9,7 @@ Param( ); - +$validateStartTime = Get-Date; $__=$__ #quiesce warnings $invocationDir = $PSScriptRoot @@ -204,17 +204,33 @@ try{ $missingFiles = ($ExpectedTestResultFiles|Where-Object{$ActualTestResultFiles -NotContains $_}) $superfluousFiles = ($ActualTestResultFiles|Where-Object{$ExpectedTestResultFiles -NotContains $_}) $matchingFiles = ($ActualTestResultFiles|Where-Object{$ExpectedTestResultFiles -Contains $_}) - Write-Warning("Matching Files:") - $matchingFiles - Write-Warning("Missing Files:") - $missingFiles - Write-Warning("Unexpected Files:") - $superfluousFiles + Log-Output("+------------------------------------------------"); + Log-Output("| Expected Test Result Files:"); + $matchingFiles|%{Log-Output("| - $_");} + Log-Output("+------------------------------------------------"); + if($missingFiles.length -gt 0){ + Log-Output("| Missing Test Result Files:"); + $missingFiles|%{Log-Output("| - $_");} + }else{ + Log-Output("| No Missing Test Result Files"); + } + Log-Output("+------------------------------------------------"); + if($superfluousFiles.length -gt 0){ + Log-Output("| Unexpected Test Result Files:") + $superfluousFiles|%{Log-Output("| - $_");} + }else{ + Log-Output("| No Unexpected Test Result Files"); + } + Log-Output("+------------------------------------------------"); if($missingFiles.Length + $superfluousFiles.Length -gt 0){ Write-Error("Missing or Unexpected Test Result Files!") } } finally{ Pop-Location + $validateEndTime = Get-Date; + Log-Output("------------------------------------") + Log-Output("Total Duration: $($validateEndTime-$validateStartTime)") + Log-Output("------------------------------------") } diff --git a/Build/tSQLt_ValidateRunAll.ps1 b/Build/tSQLt_ValidateRunAll.ps1 index 10e54f9a2..963cc6553 100644 --- a/Build/tSQLt_ValidateRunAll.ps1 +++ b/Build/tSQLt_ValidateRunAll.ps1 @@ -67,16 +67,16 @@ Function Invoke-Tests DatabaseName = $DatabaseName PrintSqlOutput = $true } - $dddbefore = Get-Date;Write-Warning("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") $parameters; Invoke-SQLFileOrQuery @parameters; - $dddafter = Get-Date;Write-Warning("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") - $dddafter-$dddbefore + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Duration: $($dddafter-$dddbefore)") - $dddbefore = Get-Date;Write-Warning("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Copy-SQLXmlToFile[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Copy-SQLXmlToFile[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") Copy-SQLXmlToFile $SqlServerConnection $DatabaseName "EXEC [tSQLt].[XmlResultFormatter]" $OutputFile - $dddafter = Get-Date;Write-Warning("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Copy-SQLXmlToFile[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") - $dddafter-$dddbefore + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Copy-SQLXmlToFile[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Duration: $($dddafter-$dddbefore)") $parameters = @{ SqlServerConnection = $SqlServerConnection @@ -84,11 +84,11 @@ Function Invoke-Tests DatabaseName = $DatabaseName Query = "EXEC tSQLt_testutil.LogMultiRunResult '$TestSetName';" } - $dddbefore = Get-Date;Write-Warning("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + $dddbefore = Get-Date;Write-Verbose("------->>BEFORE<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddbefore|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") # $parameters; Invoke-SQLFileOrQuery @parameters; - $dddafter = Get-Date;Write-Warning("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") - $dddafter-$dddbefore + $dddafter = Get-Date;Write-Verbose("------->>After<<-------(tSQLt_Validate.ps1:Invoke-Tests:Invoke-SQLFileOrQuery[$($dddafter|Get-Date -Format "yyyy:MM:dd;HH:mm:ss.fff")])") + Write-Verbose("Duration: $($dddafter-$dddbefore)") } Function Invoke-TestsFromFile diff --git a/Build/tSQLt_ValidateRuntSQLtTests.ps1 b/Build/tSQLt_ValidateRuntSQLtTests.ps1 index 9f5c89388..de54c2288 100644 --- a/Build/tSQLt_ValidateRuntSQLtTests.ps1 +++ b/Build/tSQLt_ValidateRuntSQLtTests.ps1 @@ -27,7 +27,7 @@ if($DeploySource -eq "class"){ Log-Output('Deploying tSQLt from tSQLt DacPac...') $FriendlySQLServerVersion = Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection; - $DacpacFileName = (Join-Path $SourcePath ("tSQLtDacPacs/tSQLt."+$FriendlySQLServerVersion+".dacpac") | Resolve-Path); + $DacpacFileName = (Join-Path $SourcePath ("tSQLtDacpacs/tSQLt."+$FriendlySQLServerVersion+".dacpac") | Resolve-Path); $SqlConnectionString = $SqlServerConnection.GetConnectionString($TestDbName,'DeployDacpac') & sqlpackage /a:Publish /tcs:"$SqlConnectionString" /sf:"$DacpacFileName" if($LASTEXITCODE -ne 0) { @@ -38,6 +38,18 @@ if($DeploySource -eq "class"){ # Write-Warning('-->>------------>>--') # Get-FriendlySQLServerVersion -SqlServerConnection $SqlServerConnection # Write-Warning('--<<------------<<--') +Log-Output('Run All Tests... Disabling External Access...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "DisableExternalAccess.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + Log-Output('Run All Tests... Run Bootstrap Tests...') $parameters = @{ SqlServerConnection = $SqlServerConnection @@ -112,6 +124,18 @@ $parameters = @{ } Invoke-TestsFromFile @parameters; +Log-Output('Run All Tests... Installing Assembly Key (needed for 2016 only)...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "Install(tSQLtAssemblyKey).sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + Log-Output('Run All Tests... tSQLt EXTERNAL_ACCESS_KEY_EXISTS Tests...') $parameters = @{ SqlServerConnection = $SqlServerConnection @@ -123,6 +147,18 @@ $parameters = @{ } Invoke-TestsFromFile @parameters; +Log-Output('Run All Tests... Enabling External Access...') +$parameters = @{ + SqlServerConnection = $SqlServerConnection + HelperSQLPath = $HelperSQLPath + Elevated = $true + Files = @( + (Join-Path $TestsPath "EnableExternalAccess.sql" | Resolve-Path) + ) + DatabaseName = $TestDbName +} +Invoke-SQLFileOrQuery @parameters; + Log-Output('Run All Tests... tSQLt EXTERNAL_ACCESS Tests...') $parameters = @{ SqlServerConnection = $SqlServerConnection diff --git a/CI/Azure-DevOps/AZ_MainPipeline.yml b/CI/Azure-DevOps/AZ_MainPipeline.yml index bde43f194..9863f621b 100644 --- a/CI/Azure-DevOps/AZ_MainPipeline.yml +++ b/CI/Azure-DevOps/AZ_MainPipeline.yml @@ -14,7 +14,7 @@ schedules: always: true pool: - vmImage: 'windows-latest' + vmImage: 'ubuntu-latest' parameters: # TODO, these don't work for scheduled pipelines, not even the defaults. Fix it. Hint: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/runtime-parameters?view=azure-devops&tabs=script#check-for-an-empty-parameter-object - name: VMMatrix @@ -22,14 +22,26 @@ parameters: # TODO, these don't work for scheduled pipelines, not even the defau default: # - name: SQL2012 # SQLVersionEdition: 2012Ent - - name: SQL2014 - SQLVersionEdition: 2014 + # - name: SQL2014 + # SQLVersionEdition: 2014 - name: SQL2016 SQLVersionEdition: 2016 - - name: SQL2017 - SQLVersionEdition: 2017 - - name: SQL2019 - SQLVersionEdition: 2019 + # - name: SQL2017 + # SQLVersionEdition: 2017 + # - name: SQL2019 + # SQLVersionEdition: 2019 + # - name: SQL2022 + # SQLVersionEdition: 2022 + # - name: SQL2017Linux + # SQLVersionEdition: 2017L + # - name: SQL2019Linux + # SQLVersionEdition: 2019L + # - name: SQL2022Linux + # SQLVersionEdition: 2022L + - name: CreateEnvOnly + displayName: Create Environment Only + default: false + type: boolean - name: VMPriority displayName: VM Priority type: string @@ -55,23 +67,29 @@ variables: - name: ARTIFACT_REPO_DIR value: 'tSQLtArtifactRepo' - name: CLR_ARTIFACT_DIR - value: $(Pipeline.Workspace)\$(TSQLT_REPO_DIR)\Build\output\CLRBuild + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtCLR - name: TSQLTFILES_ARTIFACT_DIR - value: $(Pipeline.Workspace)\$(TSQLT_REPO_DIR)\Build\output\tSQLtBuild + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtBuild/Artifact - name: DACPAC_ARTIFACT_DIR - value: $(Pipeline.Workspace)\$(TSQLT_REPO_DIR)\Build\output\DacpacBuild + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/DacpacBuild/Artifact - name: TSQLT_PUBLIC_ARTIFACT_DIR - value: $(Pipeline.Workspace)\$(TSQLT_REPO_DIR)\Build\output\tSQLt\public + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLt/public - name: TSQLT_VALIDATION_ARTIFACT_DIR - value: $(Pipeline.Workspace)\$(TSQLT_REPO_DIR)\Build\output\tSQLt\validation + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLt/validation - name: TSQLT_TEST_RESULTS_ARTIFACT_DIR - value: $(Pipeline.Workspace)\$(TSQLT_REPO_DIR)\Build\output\tSQLt\validation\TestResults - - name: SQLCMDPath -# value: 'C:\Program Files\Microsoft SQL Server\110\Tools\Binn' for vmImage: 'vs2017-win2016' - value: 'C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn' - - name: SQLPackagePath -# value: 'C:\Program Files\Microsoft SQL Server\150\DAC\bin' Updated to the new path on 2022-02-05 - value: 'C:\Program Files\Microsoft SQL Server\160\DAC\bin' + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLt/validation/TestResults + - name: TSQLT_BUILD_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtBuild + - name: TSQLT_TESTS_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/tSQLtTests + - name: DACPAC_BUILD_DIR + value: $(Pipeline.Workspace)/$(TSQLT_REPO_DIR)/Build/output/DacpacBuild +# - name: SQLCMDPath +# # value: 'C:/Program Files/Microsoft SQL Server/110/Tools/Binn' for vmImage: 'vs2017-win2016' +# value: 'C:/Program Files/Microsoft SQL Server/Client SDK/ODBC/170/Tools/Binn' +# - name: SQLPackagePath +# # value: 'C:/Program Files/Microsoft SQL Server/150/DAC/bin' Updated to the new path on 2022-02-05 +# value: 'C:/Program Files/Microsoft SQL Server/160/DAC/bin' resources: repositories: @@ -82,11 +100,18 @@ resources: stages: -- stage: Create_VMs +########################################################################################################## +## CREATE VMs / Containers ## +########################################################################################################## + + +- stage: Create_Environments dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel + pool: + vmImage: 'windows-latest' jobs: - - job: Create_VM + - job: Create strategy: matrix: ${{ each version in parameters.VMMatrix }}: @@ -102,7 +127,7 @@ stages: - task: AzureKeyVault@1 inputs: - azureSubscription: 'Azure DevOps Main Pipeline Service Principal' + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' KeyVaultName: 'tSQLtSigningKey' SecretsFilter: '*' RunAsPreJob: false @@ -110,10 +135,14 @@ stages: - task: PowerShell@2 name: CreateResourceGroupName inputs: + workingDirectory: "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" targetType: 'inline' script: | - Set-Location "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" - .("Build/CommonFunctionsAndMethods.ps1") + $cfam = (Join-Path "./Build/" "CommonFunctionsAndMethods.psm1" | Resolve-Path) + + Write-Host "Attempting to load module from: $cfam" + Import-Module "$cfam" -Force + Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded $ResourceGroupName = ("$(NamePreFix)" + (Get-Date).tostring('yyyyMMdd') + "_" + "$(SQLVersionName)" + "_" + "$(Build.BuildId)"); Log-Output "ResourceGroupName: $ResourceGroupName"; @@ -130,52 +159,78 @@ stages: SQLPORTMINIMUM: $(SqlPortMinimum) SQLPORTMAXIMUM: $(SqlPortMaximum) inputs: - azureSubscription: 'Azure DevOps Main Pipeline Service Principal' + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' azurePowerShellVersion: 'LatestVersion' scriptType: ps scriptLocation: inlineScript inlineScript: | - Set-Location "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" - .("Build/CommonFunctionsAndMethods.ps1") + Set-Location (Join-Path "$(Pipeline.Workspace)" "$(TSQLT_REPO_DIR)") + $cfam = (Join-Path "./Build/" "CommonFunctionsAndMethods.psm1" | Resolve-Path) + Write-Host "Attempting to load module from: $cfam" + Import-Module "$cfam" -Force + Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded $SqlPort = Get-Random -minimum $env:SQLPORTMINIMUM -maximum $env:SQLPORTMAXIMUM $SQLUserName = "$env:SQL_USER_NAME"; $SQLPwd = "$env:SQL_PASSWORD"; #TODO, randomize password, instead of taking it directly from the key vault - Log-Output '=========================================================='; - Log-Output 'Executing CreateSQLVM.ps1'; - Log-Output '=========================================================='; - $Parameters = @{ - Location="$(VMLocation)"; - Size="$(VMSize)" - ResourceGroupName="$(CreateResourceGroupName.ResourceGroupName)"; - BuildId="$(Build.BuildId)"; - VMAdminName="$env:VM_USER_NAME"; - VMAdminPwd="$env:VM_PASSWORD"; - SQLVersionEdition="$(SQLVersionEdition)"; - SQLPort="$SqlPort"; - SQLUserName="$SQLUserName"; - SQLPwd="$SQLPwd"; - VMPriority="${{ parameters.VMPriority }}"; - }; - $VMDetails = .'CI/Azure-DevOps/CreateSQLVM_azcli.ps1' @Parameters - - Log-Output '=========================================================='; - Log-Output 'Done: Executing CreateSQLVM.ps1'; - Log-Output '=========================================================='; - + if("$(SQLVersionEdition)" -like "[0-9][0-9][0-9][0-9]L"){ + $SqlPort = 1433; + $SQLUserName = 'SA' + + Log-Output '=========================================================='; + Log-Output 'Executing CreateSQLContainer.ps1'; + Log-Output '=========================================================='; + $Parameters = @{ + Location="$(VMLocation)"; + # Size="$(VMSize)" + ResourceGroupName="$(CreateResourceGroupName.ResourceGroupName)"; + BuildId="$(Build.BuildId)"; + SQLVersionEdition="$(SQLVersionEdition)"; + # SQLPort="1433"; + # SQLUserName="SA"; + SQLPwd="$SQLPwd"; + SQLCpu=3; + SQLMemory=8; + }; + $VMDetails = & 'CI/Azure-DevOps/CreateSQLContainer.ps1' @Parameters + + Log-Output '=========================================================='; + Log-Output 'Done: Executing CreateSQLContainer.ps1'; + Log-Output '=========================================================='; + }else{ + Log-Output '=========================================================='; + Log-Output 'Executing CreateSQLVM_azcli.ps1'; + Log-Output '=========================================================='; + $Parameters = @{ + Location="$(VMLocation)"; + Size="$(VMSize)" + ResourceGroupName="$(CreateResourceGroupName.ResourceGroupName)"; + BuildId="$(Build.BuildId)"; + VMAdminName="$env:VM_USER_NAME"; + VMAdminPwd="$env:VM_PASSWORD"; + SQLVersionEdition="$(SQLVersionEdition)"; + SQLPort="$SqlPort"; + SQLUserName="$SQLUserName"; + SQLPwd="$SQLPwd"; + VMPriority="${{ parameters.VMPriority }}"; + }; + $VMDetails = .'CI/Azure-DevOps/CreateSQLVM_azcli.ps1' @Parameters + + Log-Output '=========================================================='; + Log-Output 'Done: Executing CreateSQLVM_azcli.ps1'; + Log-Output '=========================================================='; + } # $SerializedVMDetails=(ConvertTo-JSON -InputObject $VMDetails -Compress); # $SerializedVMDetails; #-----------------------------------------------------------------------# - # IMPORTANT (and, you've got to be kidding me): # - # The space below is absolutely required to make the ANT Task work. # + # The space below is required to make ANT work. (Not currently in use.) # #---------------------------------------|-------------------------------# $FQDNAndPort = $VMDetails.SQLVmFQDN + ", " + $VMDetails.SQLVmPort; #---------------------------------------|-------------------------------# #-----------------------------------------------------------------------# - #TODO refactor such that the resourcegroupname is created in a previous step, so that it can be used by the delete job, even if this one is cancelled/failed. $ResourceGroupName = $VMDetails.ResourceGroupName; Write-Host "##vso[task.setvariable variable=SQLUserName;isOutput=true]$SQLUserName" Write-Host "##vso[task.setvariable variable=SQLPwd;isOutput=true]$SQLPwd" @@ -185,20 +240,63 @@ stages: # Write-Host "##vso[task.setvariable variable=SerializedVMDetails;isOutput=true]$SerializedVMDetails"; + - job: PrintSQLInfo + dependsOn: Create + strategy: + matrix: + ${{ each version in parameters.VMMatrix }}: + ${{ format('{0}', version.name) }}: + SQLVersionEdition: ${{ version.SQLVersionEdition }} + SQLVersionName: ${{ version.name }} + variables: + databaseAccessDetails: $[convertToJson(dependencies.Create.outputs)] + steps: + - checkout: none - task: PowerShell@2 - name: PrintSQLVersionInfo - env: - USER_NAME: $(tSQLt-UserForCIEnvironment-UserName) - PASSWORD: $(tSQLt-UserForCIEnvironment-Password) + name: PrintSQLInfo inputs: targetType: 'inline' script: | - $DS = Invoke-Sqlcmd -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "$(CreateSQLVMEnvironment.FQDNAndPort)" -Username "$(CreateSQLVMEnvironment.SQLUserName)" -Password "$(CreateSQLVMEnvironment.SQLPwd)" -As DataSet -TrustServerCertificate + $inputObject = @' + $(databaseAccessDetails) + '@; + $myJsonObject = ConvertFrom-JSON -InputObject $inputObject; + $SQLUserNameKey = "$(System.JobName).CreateSQLVMEnvironment.SQLUserName"; + $SQLPwdKey = "$(System.JobName).CreateSQLVMEnvironment.SQLPwd"; + $FQDNAndPortKey = "$(System.JobName).CreateSQLVMEnvironment.FQDNAndPort"; + $SQLUserName = $myJsonObject.$SQLUserNameKey; + $SQLPwd = $myJsonObject.$SQLPwdKey; + $FQDNAndPort = $myJsonObject.$FQDNAndPortKey; + + $DS = Invoke-Sqlcmd -Query "SELECT SUSER_NAME() U,SYSDATETIME() T,@@VERSION V;" -ServerInstance "$FQDNAndPort" -Username "$SQLUserName" -Password "$SQLPwd" -As DataSet -TrustServerCertificate $DS.Tables[0].Rows | %{ echo "{ $($_['U']), $($_['T']), $($_['V']) }" } + if("${{ parameters.CreateEnvOnly }}" -ieq "true"){ + Write-Host '==========================================================' -ForegroundColor Yellow; + Write-Host "Name: $(SQLVersionName)"; + Write-Host "FQDN: $FQDNAndPort"; + Write-Host "User: $SQLUserName"; + Write-Host "Pass: $SQLPwd"; + Write-Host '==========================================================' -ForegroundColor Yellow; + + Write-Warning 'This information is now public! Run the following Powershell Statement to change the password immediately!'; + + $PwdChngCommand = "`$NewPwd = -join ((40..95+97..126) | Get-Random -Count 40 | % {[char]`$_}); Write-Output `"New Password: `$NewPwd`"; Invoke-Sqlcmd -Query `"ALTER LOGIN [$SQLUserName] WITH PASSWORD = '`$NewPwd';`" -ServerInstance `"$FQDNAndPort`" -Username `"$SQLUserName`" -Password `"$SQLPwd`" -TrustServerCertificate;"; + Write-Host '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - ' -ForegroundColor Yellow; + Write-Host $PwdChngCommand + Write-Host '==========================================================' -ForegroundColor Yellow; + + } + + +########################################################################################################## +## BUILD tSQLt - PART 1 ## +########################################################################################################## + - stage: Build_tSQLt_Part1 dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel + condition: eq(${{ parameters.CreateEnvOnly }}, false) jobs: @@ -206,8 +304,8 @@ stages: timeoutInMinutes: 10 cancelTimeoutInMinutes: 2 - variables: - CertificatePath: '$(Build.Repository.LocalPath)\tsqltclr\OfficialSigningKey\tSQLtOfficialSigningKey.pfx' + # variables: + # CertificatePath: '$(Build.Repository.LocalPath)/tsqltclr/OfficialSigningKey/tSQLtOfficialSigningKey.pfx' steps: @@ -218,43 +316,31 @@ stages: - task: AzureKeyVault@1 inputs: - azureSubscription: 'Azure DevOps Main Pipeline Service Principal' + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' KeyVaultName: 'tSQLtSigningKey' - task: PowerShell@2 - name: Install_tSQLt_OfficialSigningKey + name: CompileCLR inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ targetType: 'inline' script: | + $pfxSecretBytes = [System.Convert]::FromBase64String('$(tSQLtOfficialSigningKey-Base64)') - $pfxPath = "$(Build.SourcesDirectory)/Build/tSQLtOfficialSigningKey.pfx" - [System.IO.File]::WriteAllBytes($pfxPath, $pfxSecretBytes) - &"$(Build.SourcesDirectory)/Build/SnInstallPfx" $(Build.SourcesDirectory)/Build/tSQLtOfficialSigningKey.pfx '$(tSQLtSigningKeyPassword)' tSQLt_OfficialSigningKey + $pfxFilePath = (Join-Path "$(Build.SourcesDirectory)" "/Build/tSQLtOfficialSigningKey.pfx") + [System.IO.File]::WriteAllBytes($pfxFilePath, $pfxSecretBytes) + $pfxPassword = (ConvertTo-SecureString -String '$(tSQLtSigningKeyPassword)' -Force -AsPlainText) - - task: MSBuild@1 - displayName: 'Build solution tSQLtCLR/tSQLtCLR.sln' - inputs: - solution: tSQLtCLR/tSQLtCLR.sln - platform: 'Any CPU' - configuration: CruiseControl + & ./tSQLt_BuildCLR.ps1 -pfxFilePath $pfxFilePath -pfxPassword $pfxPassword - task: CopyFiles@2 displayName: 'Copy all dll files to the ArtifactStagingDirectory' inputs: - SourceFolder: tSQLtCLR - Contents: '*/bin/*/*.dll' - TargetFolder: '$(Build.ArtifactStagingDirectory)/tSQLtCLR' + SourceFolder: ./Build/output/tSQLtCLR + Contents: 'tSQLtCLR.zip' + TargetFolder: '$(Build.ArtifactStagingDirectory)/tSQLtCLR.zip' flattenFolders: true - - task: ArchiveFiles@2 - inputs: - rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/tSQLtCLR' - includeRootFolder: false - archiveType: 'zip' - archiveFile: '$(Build.ArtifactStagingDirectory)/tSQLtCLR.zip' - replaceExistingArchive: true - verbose: true - - task: PublishPipelineArtifact@1 name: PublishCLRArtifact inputs: @@ -274,6 +360,14 @@ stages: lfs: false path: $(TSQLT_REPO_DIR) + - task: PowerShell@2 + name: CreateArtifactDir + inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + mkdir -p $(CLR_ARTIFACT_DIR) + - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' @@ -281,13 +375,21 @@ stages: itemPattern: '*.zip' targetPath: '$(CLR_ARTIFACT_DIR)' - - task: Ant@1 - displayName: 'Ant -debug Build/tSQLt.build.xml' + - task: PowerShell@2 + name: Build_tSQLt inputs: - buildFile: Build/tSQLt.build.xml - options: ' -D"commit.id"="$(Build.BuildId)" ' - targets: all - publishJUnitResults: false + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + & ./tSQLt_Build.ps1 + + - task: PowerShell@2 + name: Build_tSQLtTests + inputs: + workingDirectory: $(Build.SourcesDirectory)/Build/ + targetType: 'inline' + script: | + & ./tSQLt_BuildTests.ps1 - task: PowerShell@2 name: CreateArtifact @@ -295,25 +397,34 @@ stages: targetType: 'inline' failOnStderr: true script: | - $basePath = "$(Build.SourcesDirectory)\Build\output\tSQLtBuild\"; - $artifactPath = ($basePath+"Artifact\"); + $basePath = "$(Build.SourcesDirectory)/Build/output/"; + $artifactPath = '$(TSQLTFILES_ARTIFACT_DIR)'; New-Item -Path $artifactPath -ItemType directory -Force - $artifactFiles = @("ReadMe.txt","CommitId.txt","CreateBuildLog.sql","GetFriendlySQLServerVersion.sql","tSQLt.tests.zip","tSQLtFacade.zip","tSQLtFiles.zip","tSQLtSnippets(SQLPrompt).zip","Version.txt"); - Get-ChildItem -Path ($basePath + "*") -Include $artifactFiles | Copy-Item -Destination "$artifactPath"; + # $toBeCopied = @("Version.txt", "CommitId.txt", "tSQLt.Private_GetAssemblyKeyBytes.sql", "GetFriendlySQLServerVersion.sql", "CreateBuildLog.sql"); + $artifactFiles = @("Version.txt","CommitId.txt", "tSQLt.Private_GetAssemblyKeyBytes.sql", "GetFriendlySQLServerVersion.sql","CreateBuildLog.sql","tSQLtFiles.zip","tSQLtSnippets(SQLPrompt).zip"); + Get-ChildItem -Path ($basePath + "tSQLtBuild/*") -Include $artifactFiles | Copy-Item -Destination "$artifactPath"; + $artifactFiles = @("tSQLt.tests.zip"); + Get-ChildItem -Path ($basePath + "tSQLtTests/*") -Include $artifactFiles | Copy-Item -Destination "$artifactPath"; Set-Content -Path ($artifactPath+"CommitId.txt") -Value "$(Build.SourceVersion)" - task: PublishPipelineArtifact@1 name: PublishtSQLtFilesArtifact inputs: - targetPath: '$(TSQLTFILES_ARTIFACT_DIR)\Artifact' + targetPath: '$(TSQLTFILES_ARTIFACT_DIR)' artifact: 'tSQLtFilesArtifact' publishLocation: 'pipeline' + +########################################################################################################## +## BUILD tSQLt - PART 2 ## +########################################################################################################## + + - stage: Build_tSQLt_Part2 dependsOn: - Build_tSQLt_Part1 - - Create_VMs + - Create_Environments jobs: - job: Build_Dacpac @@ -325,7 +436,7 @@ stages: SQLVersionEdition: ${{ version.SQLVersionEdition }} variables: - databaseAccessDetails: $[convertToJson(stageDependencies.Create_VMs.Create_VM.outputs)] + databaseAccessDetails: $[convertToJson(stageDependencies.Create_Environments.Create.outputs)] steps: - checkout: self @@ -341,11 +452,29 @@ stages: targetPath: '$(TSQLTFILES_ARTIFACT_DIR)' - task: PowerShell@2 - name: FacadeBuildDacpac + displayName: 'Install SqlServer Module' + inputs: + pwsh: true + targetType: 'inline' + script: | + # Check if the SqlServer module is installed + $module = "SqlServer" + if (-not (Get-Module -ListAvailable -Name $module)) { + # Install the SqlServer module + Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber + } + Import-Module $module + + + - task: PowerShell@2 + name: BuildDacpac inputs: + pwsh: true targetType: 'inline' script: | - Set-Location "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" + $BuildDir = (Join-Path "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" "Build"|Resolve-Path); + Set-Location $BuildDir; + . (Join-Path $BuildDir 'SQLServerConnection.ps1'); $inputObject = @' $(databaseAccessDetails) @@ -358,17 +487,22 @@ stages: $FQDNAndPortKey = "$(System.JobName).CreateSQLVMEnvironment.FQDNAndPort"; $SQLUserName = $myJsonObject.$SQLUserNameKey; - $SQLPwd = $myJsonObject.$SQLPwdKey; + $SQLPwd = (ConvertTo-SecureString $myJsonObject.$SQLPwdKey -AsPlainText); $FQDNAndPort = $myJsonObject.$FQDNAndPortKey; + $ApplicationName = "$(Build.DefinitionName)-$(Build.BuildId)-$(System.StageName)-$(System.JobId)" + $DatabaseName = "$(buildDatabase)_dacpac_src" + $SqlServerConnection = [SqlServerConnection]::new($FQDNAndPort,$SQLUserName,$SQLPwd,$ApplicationName); - .\Build\SetupDacpacBuild.ps1 -ErrorAction Stop - .\Build\FacadeBuildDacpac.ps1 -ErrorAction Stop -ServerName "$FQDNAndPort" -DatabaseName "$(buildDatabase)" -Login " -U $SQLUserName -P $SQLPwd" -SqlCmdPath "$(SQLCMDPath)" -SqlPackagePath "$(SQLPackagePath)" - .\Build\BuildtSQLtDacpac.ps1 -ErrorAction Stop -ServerName "$FQDNAndPort" -DatabaseName "$(buildDatabase)_dacpac_src" -Login " -U $SQLUserName -P $SQLPwd" -SqlCmdPath "$(SQLCMDPath)" -SqlPackagePath "$(SQLPackagePath)" + $__ = New-Item -ItemType "directory" -Path "./output/tSQLtTests/" + # $__ = New-Item -ItemType "directory" -Path "./output/tSQLtBuild/" + Move-Item (Join-Path '$(TSQLTFILES_ARTIFACT_DIR)' 'tSQLt.tests.zip') "./output/tSQLtTests/" + Move-Item (Join-Path '$(TSQLTFILES_ARTIFACT_DIR)' 'tSQLtFiles.zip') "./output/tSQLtBuild/" + & ./tSQLt_BuildDacpac.ps1 -SqlServerConnection $SqlServerConnection -DacPacDatabaseName $DatabaseName - task: PublishPipelineArtifact@1 name: PublishtSQLtDacpacArtifact inputs: - targetPath: '$(DACPAC_ARTIFACT_DIR)' + targetPath: '$(DACPAC_BUILD_DIR)' artifact: 'tSQLtDacpacArtifact_$(System.JobName)' publishLocation: 'pipeline' @@ -387,7 +521,7 @@ stages: inputs: buildType: 'current' artifactName: 'tSQLtFilesArtifact' - targetPath: '$(TSQLTFILES_ARTIFACT_DIR)' + targetPath: '$(Pipeline.Workspace)/tSQLtArtifactTmp' - task: DownloadPipelineArtifact@2 name: DownloadtSQLtDacpacArtifact @@ -397,15 +531,39 @@ stages: targetPath: '$(Pipeline.Workspace)/dacpacArtifactTmp' - task: PowerShell@2 - name: CopyDacpacsToOutputDir + name: CopyFilesToOutputDirs inputs: targetType: 'inline' failOnStderr: true script: | + $__ = New-Item -Path "$(TSQLT_BUILD_DIR)" -ItemType directory -Force; + $__ = New-Item -Path "$(TSQLT_TESTS_DIR)" -ItemType directory -Force; + $__ = New-Item -Path "$(DACPAC_BUILD_DIR)" -ItemType directory -Force; + + Get-ChildItem -Path "$(Pipeline.Workspace)/tSQLtArtifactTmp/" -Recurse; + + $files = @( + "$(Pipeline.Workspace)/tSQLtArtifactTmp/Version.txt", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/CommitId.txt", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/CreateBuildLog.sql", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/GetFriendlySQLServerVersion.sql", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/tSQLtSnippets(SQLPrompt).zip", + "$(Pipeline.Workspace)/tSQLtArtifactTmp/tSQLtFiles.zip" + ); + $files|%{Move-Item $_ "$(TSQLT_BUILD_DIR)"} + Move-Item (Join-Path '$(Pipeline.Workspace)/tSQLtArtifactTmp' 'tSQLt.tests.zip') "$(TSQLT_TESTS_DIR)" + Get-ChildItem -Path "$(Pipeline.Workspace)/dacpacArtifactTmp" -Filter *.dacpac -Recurse; - New-Item -Path "$(DACPAC_ARTIFACT_DIR)" -ItemType directory -Force; - Get-ChildItem -Path "$(Pipeline.Workspace)/dacpacArtifactTmp" -Filter *.dacpac -Recurse | Copy-Item -Destination "$(DACPAC_ARTIFACT_DIR)" - Get-ChildItem -Path "$(DACPAC_ARTIFACT_DIR)" -Recurse; + Get-ChildItem -Path "$(Pipeline.Workspace)/dacpacArtifactTmp" -Filter *.dacpac -Recurse | Copy-Item -Destination "$(DACPAC_BUILD_DIR)" + + Write-Host "CopyFilesToOutputDirs Results:" + Write-Host "----------------------------------------------------------------" + Get-ChildItem "$(TSQLT_BUILD_DIR)" -Recurse; + Write-Host "----------------------------------------------------------------" + Get-ChildItem "$(TSQLT_TESTS_DIR)" -Recurse; + Write-Host "----------------------------------------------------------------" + Get-ChildItem "$(DACPAC_BUILD_DIR)" -Recurse; + Write-Host "----------------------------------------------------------------" - task: PowerShell@2 name: BuildtSQLtZip @@ -413,7 +571,7 @@ stages: targetType: 'inline' script: | Set-Location "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)"; - .\Build\BuildtSQLtZip.ps1 + ./Build/tSQLt_BuildPackage.ps1 Get-ChildItem -Path "$(TSQLT_PUBLIC_ARTIFACT_DIR)" -Recurse; Get-ChildItem -Path "$(TSQLT_VALIDATION_ARTIFACT_DIR)" -Recurse; @@ -426,7 +584,7 @@ stages: - task: AzureKeyVault@1 inputs: - azureSubscription: 'Azure DevOps Main Pipeline Service Principal' + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' KeyVaultName: 'tSQLtSigningKey' - task: PowerShell@2 @@ -441,7 +599,7 @@ stages: script: | $CheckIfGitOk = {param($isOk);if(-not $isOk){Write-Host "##vso[task.logissue type=error]GIT Failed!";throw "GIT Failed!"}}; - Set-Location "$(Pipeline.Workspace)\$(ARTIFACT_REPO_DIR)\"; + Set-Location "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/"; Write-Host ("ArtifactBranchName: {0}" -f "$(ArtifactBranchName)"); try{ git config --global user.email "$env:GITHUB_EMAIL" @@ -460,11 +618,11 @@ stages: git rm -r *.* $CheckIfGitOk.invoke($?); - New-Item -Path "$(Pipeline.Workspace)\$(ARTIFACT_REPO_DIR)\public" -ItemType directory -Force; - New-Item -Path "$(Pipeline.Workspace)\$(ARTIFACT_REPO_DIR)\validation" -ItemType directory -Force; + New-Item -Path "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/public" -ItemType directory -Force; + New-Item -Path "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/validation" -ItemType directory -Force; - Copy-Item -Path "$(TSQLT_PUBLIC_ARTIFACT_DIR)\*" -Destination "$(Pipeline.Workspace)\$(ARTIFACT_REPO_DIR)\public" -Recurse -Verbose - Copy-Item -Path "$(TSQLT_VALIDATION_ARTIFACT_DIR)\*" -Destination "$(Pipeline.Workspace)\$(ARTIFACT_REPO_DIR)\validation" -Recurse -Verbose + Copy-Item -Path "$(TSQLT_PUBLIC_ARTIFACT_DIR)/*" -Destination "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/public" -Recurse -Verbose + Copy-Item -Path "$(TSQLT_VALIDATION_ARTIFACT_DIR)/*" -Destination "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/validation" -Recurse -Verbose Get-ChildItem -Path "./public" -Recurse; Get-ChildItem -Path "./validation" -Recurse; @@ -484,11 +642,17 @@ stages: } git status + +########################################################################################################## +## VALIDATE ## +########################################################################################################## + + - stage: Validate displayName: ValidateAll dependsOn: - Build_tSQLt_Part2 - - Create_VMs + - Create_Environments jobs: @@ -500,7 +664,7 @@ stages: SQLVersionEdition: ${{ version.SQLVersionEdition }} variables: - databaseAccessDetails: $[convertToJson(stageDependencies.Create_VMs.Create_VM.outputs)] + databaseAccessDetails: $[convertToJson(stageDependencies.Create_Environments.Create.outputs)] steps: - checkout: self @@ -545,10 +709,30 @@ stages: - task: PowerShell@2 - name: SetupVariables + displayName: 'Install SqlServer Module' + inputs: + pwsh: true + targetType: 'inline' + script: | + # Check if the SqlServer module is installed + $module = "SqlServer" + if (-not (Get-Module -ListAvailable -Name $module)) { + # Install the SqlServer module + Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber + } + Import-Module $module + + + - task: PowerShell@2 + name: tSQLtValidateBuild inputs: + pwsh: true targetType: 'inline' script: | + $BuildDir = (Join-Path "$(Pipeline.Workspace)/$(TSQLT_REPO_DIR)" "Build"|Resolve-Path); + Set-Location $BuildDir; + . (Join-Path $BuildDir 'SQLServerConnection.ps1'); + $inputObject = @' $(databaseAccessDetails) '@; @@ -560,32 +744,40 @@ stages: $FQDNAndPortKey = "$(System.JobName).CreateSQLVMEnvironment.FQDNAndPort"; $SQLUserName = $myJsonObject.$SQLUserNameKey; - $SQLPwd = $myJsonObject.$SQLPwdKey; + $SQLPwd = (ConvertTo-SecureString $myJsonObject.$SQLPwdKey -AsPlainText); $FQDNAndPort = $myJsonObject.$FQDNAndPortKey; - - Write-Host "##vso[task.setvariable variable=SQLUserName;isOutput=true]$SQLUserName" - Write-Host "##vso[task.setvariable variable=SQLPwd;isOutput=true]$SQLPwd" - Write-Host "##vso[task.setvariable variable=FQDNAndPort;isOutput=true]$FQDNAndPort" - - - task: CmdLine@2 - name: tSQLtValidateBuild - inputs: - script: | - cd /d $(Pipeline.Workspace)\$(TSQLT_REPO_DIR) - ECHO ON - SET SQLInstanceName=$(SetupVariables.FQDNAndPort) - SET DBName=$(buildDatabase) - SET DBLogin=-U $(SetupVariables.SQLUserName) -P $(SetupVariables.SQLPwd) - SET SQLCMDPath=$(SQLCMDPath) - SET SQLPackagePath=$(SQLPackagePath) - echo %SQLInstanceName% - echo %DBName% - echo %SQLCMDPath% - echo %SQLPackagePath% - type %0 - - Build\LocalValidateBuild.bat "." "." "%SQLCMDPath%" "%SQLInstanceName%" tSQLt_Dev " %DBLogin%" "%SQLPackagePath%" -v || goto :error - :error + $ApplicationName = "$(Build.DefinitionName)-$(Build.BuildId)-$(System.StageName)-$(System.JobId)" + $DatabaseName = "$(buildDatabase)_dacpac_src" + $SqlServerConnection = [SqlServerConnection]::new($FQDNAndPort,$SQLUserName,$SQLPwd,$ApplicationName); + + $parameters = @{ + SqlServerConnection = $SqlServerConnection + MainTestDb = 'tSQLt.TmpBuild.ValidateBuild' + DacpacTestDb = 'tSQLt.TmpBuild.ValidateDacPac' + ExampleTestDb = 'tSQLt.TmpBuild.ValidateExample' + } + + & ./tSQLt_Validate.ps1 @parameters + + # - task: CmdLine@2 + # name: tSQLtValidateBuild + # inputs: + # script: | + # cd /d $(Pipeline.Workspace)/$(TSQLT_REPO_DIR) + # ECHO ON + # SET SQLInstanceName=$(SetupVariables.FQDNAndPort) + # SET DBName=$(buildDatabase) + # SET DBLogin=-U $(SetupVariables.SQLUserName) -P $(SetupVariables.SQLPwd) + # SET SQLCMDPath=$(SQLCMDPath) + # SET SQLPackagePath=$(SQLPackagePath) + # echo %SQLInstanceName% + # echo %DBName% + # echo %SQLCMDPath% + # echo %SQLPackagePath% + # type %0 + + # Build/LocalValidateBuild.bat "." "." "%SQLCMDPath%" "%SQLInstanceName%" tSQLt_Dev " %DBLogin%" "%SQLPackagePath%" -v || goto :error + # :error - task: PublishTestResults@2 @@ -618,7 +810,7 @@ stages: $TagName = "$(SQLVersionEdition)_$(Build.BuildId)" - Set-Location "$(Pipeline.Workspace)\$(ARTIFACT_REPO_DIR)\"; + Set-Location "$(Pipeline.Workspace)/$(ARTIFACT_REPO_DIR)/"; Write-Host ("ArtifactBranchName: {0}" -f "$(ArtifactBranchName)"); try{ @@ -639,17 +831,24 @@ stages: throw "git failed. See prior errors."; } - ##--##--##--##--##--##--##--##--##--##---##--##--##--##--##--##--##--##--##--## + +########################################################################################################## +## DELETE RESOURCES ## +########################################################################################################## + - stage: Delete_Resources displayName: Delete Pipeline Resources dependsOn: - - Create_VMs + - Create_Environments - Validate - condition: always() - jobs: + condition: not(eq(${{ parameters.CreateEnvOnly }}, true)) + pool: + vmImage: 'windows-latest' + + jobs: - job: Delete_VM strategy: @@ -657,28 +856,36 @@ stages: ${{ each version in parameters.VMMatrix }}: ${{ format('{0}', version.name) }}: SQLVersionEdition: ${{ version.SQLVersionEdition }} - + variables: - databaseAccessDetails: $[convertToJson(stageDependencies.Create_VMs.Create_VM.outputs)] + databaseAccessDetails: $[convertToJson(stageDependencies.Create_Environments.Create.outputs)] steps: + - checkout: self + clean: true + lfs: false + path: $(TSQLT_REPO_DIR) + - task: AzureCLI@2 name: DeleteAzureVM inputs: - azureSubscription: 'Azure DevOps Main Pipeline Service Principal' + azureSubscription: 'tSQLt CI - Main Pipeline - Service Connection' azurePowerShellVersion: 'LatestVersion' scriptType: ps scriptLocation: inlineScript inlineScript: | + Set-Location (Join-Path "$(Pipeline.Workspace)" "$(TSQLT_REPO_DIR)") + $cfam = (Join-Path "./Build/" "CommonFunctionsAndMethods.psm1" | Resolve-Path) + Write-Host "Attempting to load module from: $cfam" + Import-Module "$cfam" -Force + Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + $inputObject = @' $(databaseAccessDetails) '@; $myJsonObject = ConvertFrom-JSON -InputObject $inputObject; $ResourceGroupNameKey = "$(System.JobName).CreateResourceGroupName.ResourceGroupName"; $ResourceGroupName = $myJsonObject.$ResourceGroupNameKey; - - Set-Location $(Build.Repository.LocalPath) - .("Build/CommonFunctionsAndMethods.ps1") - + $ResourceGroupName | Log-Output; az group delete --name $ResourceGroupName --yes diff --git a/CI/Azure-DevOps/AZ_NightlyCleanup.yml b/CI/Azure-DevOps/AZ_NightlyCleanup.yml index b88be5c78..c06eb4e90 100644 --- a/CI/Azure-DevOps/AZ_NightlyCleanup.yml +++ b/CI/Azure-DevOps/AZ_NightlyCleanup.yml @@ -4,7 +4,7 @@ # https://aka.ms/yaml pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-latest' #requires windows for azure authentication schedules: - cron: 1 16 * * * @@ -35,11 +35,10 @@ steps: AZURE_DEVOPS_EXT_PAT: $(tSQLtCIAzureCLIPatToken) ## black magic: this token ^^^^ is required to make "az pipelines runs show" work below inputs: - azureSubscription: 'tSQLt CI Subscription(58c04a99-5b92-410c-9e41-10262f68ca80)' + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' scriptType: 'ps' scriptLocation: 'inlineScript' inlineScript: | - ##Functions $GetAll_tSQLtCI_RGs = { param(); @@ -141,7 +140,7 @@ steps: - task: AzurePowerShell@4 name: print_stuff inputs: - azureSubscription: 'tSQLt CI Subscription(58c04a99-5b92-410c-9e41-10262f68ca80)' + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' azurePowerShellVersion: 'LatestVersion' failOnStandardError: True ScriptType: 'InlineScript' @@ -159,7 +158,7 @@ steps: condition: succeeded() name: Delete_RGs inputs: - azureSubscription: 'tSQLt CI Subscription(58c04a99-5b92-410c-9e41-10262f68ca80)' + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' azurePowerShellVersion: 'LatestVersion' FailOnStandardError: true ScriptType: 'InlineScript' @@ -225,7 +224,7 @@ steps: name: AssertDeleteSuccessful condition: always() inputs: - azureSubscription: 'tSQLt CI Subscription(58c04a99-5b92-410c-9e41-10262f68ca80)' + azureSubscription: 'tSQLt CI - Nightly Cleanup - Service Connection' azurePowerShellVersion: 'LatestVersion' failOnStandardError: True ScriptType: 'InlineScript' diff --git a/CI/Azure-DevOps/CreateSQLContainer.ps1 b/CI/Azure-DevOps/CreateSQLContainer.ps1 new file mode 100644 index 000000000..b6a95919b --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLContainer.ps1 @@ -0,0 +1,98 @@ +<# USAGE: ./CreateSQLContainer.ps1 -Location "East US 2" -ResourceGroupName "myTestResourceGroup" -SQLVersionEdition "2022L" -SQLPwd "aoeihag;ladjfalkj46" -BuildId "001" #> +# using module "../../Build/CommonFunctionsAndMethods.psm1"; + +Param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Location, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroupName, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $BuildId, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLVersionEdition, + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SQLPwd, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][int] $SQLCpu = 4, + [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][int] $SQLMemory = 8 +); +Write-Host "Starting execution of CreateSQLContainer.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path (Join-Path $invocationDir "../../Build/") "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Host "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded + +$SQLPort = 1433; +$SQLUserName = 'SA'; + +$dir = $invocationDir; +$projectDir = Split-Path (Split-Path $dir); + +Log-Output "FileLocation: ", $dir; +Log-Output "Project Location: ", $projectDir; + + +Log-Output "*---------------*"; +Log-Output (az --version|Out-String); +Log-Output (bicep --version|Out-String); +Log-Output ($psversiontable|Out-String); +Log-Output "*---------------*"; + + +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "<-> <->"; +Log-Output "<-> START 1 <->"; +Log-Output "<-> <->"; +Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->"; +Log-Output "Parameters:"; +Log-Output "ResourceGroupName:", $ResourceGroupName; +Log-Output "Location:", $Location; +Log-Output "BuildId:", $BuildId; +Log-Output "SQLVersionEdition:", $SQLVersionEdition; +Log-Output "SQLPort:", $SQLPort; +Log-Output "<-> END 1 <-><-><-><-><-><-><-><-><-><-><-><-><->"; + +<# FYI Usage: $SQLVersionEditionHash.$SQLVersionEdition.offer = "SQL2016SP2-WS2016" #> +$SQLVersionEditionHash = @{ + "2017L"=@{"sqlversion"="2017";"image"="mcr.microsoft.com/mssql/server:2017-latest";}; + "2019L"=@{"sqlversion"="2019";"image"="mcr.microsoft.com/mssql/server:2019-latest";} + "2022L"=@{"sqlversion"="2022";"image"="mcr.microsoft.com/mssql/server:2022-latest";} +}; + +$SQLVersionEditionInfo = $SQLVersionEditionHash.$SQLVersionEdition; +Log-Output "SQLVersionEditionInfo: ", $SQLVersionEditionInfo; +$ContainerName = ("C{0}-{1}###############" -f $BuildId,$SQLVersionEdition).substring(0,15).replace('#','').ToLower() +$ContainerImage = $SQLVersionEditionInfo.image + +Log-Output 'Creating SQL Server Container resources' + +$templatePath = (Join-Path $invocationDir 'CreateSQLContainerTemplate.bicep' | Resolve-Path); +Log-Output "*---------------*"; +Log-Output $templatePath +Log-Output "*---------------*"; + +$deploymentResult = (az deployment sub create --name $ContainerName --location $Location --template-file $templatePath --parameters location=$Location containerName=$ContainerName sqlServerImage=$ContainerImage newResourceGroupName=$ResourceGroupName saPassword=$SQLPwd cpu=$SQLCpu memory=$SQLMemory); + +Log-Output 'Done: Creating SQL Server Container' +Log-Output 'Prep SQL Server for tSQLt Build' +# $deploymentResult +$outputs = ($deploymentResult|ConvertFrom-Json).properties.outputs +$ipAddress = $outputs.ipAddress.value +$Port = $outputs.Port.value +Log-Output "*---------------*"; +Log-Output "SQL Server address: $ipAddress,$Port" +Log-Output "*---------------*"; + +$GetSQLVersionPath = (Join-Path $invocationDir 'GetSQLServerVersion.sql' | Resolve-Path); +$DS = Invoke-Sqlcmd -InputFile $GetSQLVersionPath -ServerInstance "$ipAddress,$Port" -Username "$SQLUserName" -Password "$SQLPwd" -TrustServerCertificate -As DataSet +$DS.Tables[0].Rows | ForEach-Object{ Log-Output "{ $($_['LoginName']), $($_['TimeStamp']), $($_['VersionDetail']), $($_['ProductVersion']), $($_['ProductLevel']), $($_['SqlVersion']) }" } + +$ActualSQLVersion = $DS.Tables[0].Rows[0]['SqlVersion']; +Log-Output "Actual SQL Version:",$ActualSQLVersion; + +Log-Output 'Done: Prep SQL Server for tSQLt Build'; + +Return @{ + "VmName"="$ContainerName"; + "ResourceGroupName"="$ResourceGroupName"; + "SQLVmFQDN"="$ipAddress"; ##[vmname].[region].cloudapp.azure.com + "SQLVmPort"="$SQLPort"; ##1433 + "SQLVersionEdition"="$SQLVersionEdition"; ##2012Ent +}; diff --git a/CI/Azure-DevOps/CreateSQLContainerAndIpAddressModule.bicep b/CI/Azure-DevOps/CreateSQLContainerAndIpAddressModule.bicep new file mode 100644 index 000000000..5f952f748 --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLContainerAndIpAddressModule.bicep @@ -0,0 +1,59 @@ +param location string = resourceGroup().location +param containerName string +param sqlServerImage string +param cpu int +param memory int +@secure() +param saPassword string + +var containerGroupName = '${containerName}-group' +var sqlPort = 1433 + +resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2021-10-01' = { + name: containerGroupName + location: location + properties: { + containers: [ + { + name: containerName + properties: { + image: sqlServerImage + resources: { + requests: { + cpu: cpu + memoryInGB: memory + } + } + environmentVariables: [ + { + name: 'ACCEPT_EULA' + value: 'Y' + } + { + name: 'MSSQL_SA_PASSWORD' + secureValue: saPassword + } + ] + ports: [ + { + port: sqlPort + } + ] + } + } + ] + osType: 'Linux' + ipAddress: { + type: 'Public' + ports: [ + { + protocol: 'tcp' + port: sqlPort + } + ] + } + } +} + +output ipAddress string = containerGroup.properties.ipAddress.ip +output Port int = containerGroup.properties.ipAddress.ports[0].port diff --git a/CI/Azure-DevOps/CreateSQLContainerTemplate.bicep b/CI/Azure-DevOps/CreateSQLContainerTemplate.bicep new file mode 100644 index 000000000..dd0798986 --- /dev/null +++ b/CI/Azure-DevOps/CreateSQLContainerTemplate.bicep @@ -0,0 +1,37 @@ +targetScope='subscription' + +param location string = 'eastus2' +param newResourceGroupName string = 'sqlserver-container-test-rg' +param containerName string = 'sqlserver2022' +param sqlServerImage string = 'mcr.microsoft.com/mssql/server:2022-latest' +@secure() +param saPassword string +param cpu int = 4 +param memory int = 8 + + +resource newResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: newResourceGroupName + location: location + tags: { + Department: 'tSQLtCI' + Ephemeral: 'True' + } +} + + +module containers './CreateSQLContainerAndIpAddressModule.bicep' = { + name: 'deployContainers' + scope: newResourceGroup + params: { + location: location + containerName: containerName + sqlServerImage: sqlServerImage + cpu:cpu + memory:memory + saPassword: saPassword + } +} + +output ipAddress string = containers.outputs.ipAddress +output Port int = containers.outputs.Port diff --git a/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 b/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 index cc61a18bd..4376c65ff 100644 --- a/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 +++ b/CI/Azure-DevOps/CreateSQLVM_azcli.ps1 @@ -19,15 +19,18 @@ Param( [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $VMPriority ); -$scriptpath = $MyInvocation.MyCommand.Path; -$dir = Split-Path $scriptpath; -$projectDir = Split-Path (Split-Path $dir); +Write-Host "Starting execution of CreateSQLVM_azcli.ps1" +$__=$__ #quiesce warnings +$invocationDir = $PSScriptRoot +Push-Location -Path $invocationDir +$cfam = (Join-Path (Join-Path $invocationDir "../../Build/") "CommonFunctionsAndMethods.psm1" | Resolve-Path) +Write-Host "Attempting to load module from: $cfam" +Import-Module "$cfam" -Force -Verbose +Get-Module -Name CommonFunctionsAndMethods # Verify if module is loaded -.($projectDir+"\Build\CommonFunctionsAndMethods.ps1") Log-Output "<-><-><-><-><-><-><-><-><-><-><-><-><-><->"; -Log-Output "FileLocation: ", $dir; -Log-Output "Project Location: ", $projectDir; +Log-Output "FileLocation: ", $invocationDir; Log-Output "Parameters: ---------------------------"; Log-Output "Location:", $Location; Log-Output "Size:", $Size; @@ -64,20 +67,22 @@ $SQLVersionEditionHash = @{ "2008R2Std"=@{"sqlversion"="2008R2";"offer"="SQL2008R2SP3-WS2008R2SP1";"publisher"="microsoftsqlserver";"sku"="Standard";"osType"="Windows";"version"="latest";"bicep"="CreateSqlVirtualMachineTemplate-2008R2.bicep"}; #MicrosoftSQLServer:SQL2008R2SP3-WS2008R2SP1:Standard:latest "2012Ent"=@{"sqlversion"="2012";"offer"="SQL2012SP4-WS2012R2";"publisher"="microsoftsqlserver";"sku"="Enterprise";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:SQL2012SP4-WS2012R2:Enterprise:latest "2014"=@{"sqlversion"="2014";"offer"="sql2014sp3-ws2012r2";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2014sp3-ws2012r2:sqldev:latest - "2016"=@{"sqlversion"="2016";"offer"="SQL2016SP2-WS2016";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2016sp2-ws2019:sqldev:latest + "2016"=@{"sqlversion"="2016";"offer"="sql2016sp3-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2016sp2-ws2019:sqldev:latest "2017"=@{"sqlversion"="2017";"offer"="sql2017-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"}; #MicrosoftSQLServer:sql2017-ws2019:sqldev:latest - "2019"=@{"sqlversion"="2019";"offer"="sql2019-ws2019";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"} #MicrosoftSQLServer:sql2019-ws2019:sqldev:latest + "2019"=@{"sqlversion"="2019";"offer"="sql2019-ws2022";"publisher"="microsoftsqlserver";"sku"="sqldev";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"} #MicrosoftSQLServer:sql2019-ws2019:sqldev:latest + "2022"=@{"sqlversion"="2022";"offer"="sql2022-ws2022";"publisher"="microsoftsqlserver";"sku"="sqldev-gen2";"osType"="Windows";"version"="latest";"bicep"="CreateSQLVirtualMachineTemplate.bicep"} #MicrosoftSQLServer:sql2022-ws2022:sqldev:latest }; $SQLVersionEditionInfo = $SQLVersionEditionHash.$SQLVersionEdition; $ImageUrn = $SQLVersionEditionInfo.publisher+":"+$SQLVersionEditionInfo.offer+":"+$SQLVersionEditionInfo.sku+":"+$SQLVersionEditionInfo.version; -$TemplateFile = $dir + "/" + $SQLVersionEditionInfo.bicep; +$TemplateFile = (Join-Path $invocationDir $SQLVersionEditionInfo.bicep | Resolve-Path); Log-Output "ImageUrn: ", $ImageUrn; Log-Output "SQLVersionEditionInfo: ", $SQLVersionEditionInfo; Log-Output "TemplateFile: ", $TemplateFile; Log-Output "START: Creating Resource Group $ResourceGroupName"; -$output = az group create --location "$Location" --name "$ResourceGroupName" | ConvertFrom-Json; + +$output = az group create --location "$Location" --name "$ResourceGroupName" --tags Department="tSQLtCI" Ephemeral="True" | ConvertFrom-Json; if (!$output) { Write-Error "Error creating Resource Group"; return @@ -177,7 +182,8 @@ $SQLVM|Out-String|Log-Output; Log-Output 'DONE: Applying SqlVM Config' Log-Output 'START: Prep SQL Server for tSQLt Build' -$DS = Invoke-Sqlcmd -InputFile "$dir/GetSQLServerVersion.sql" -ServerInstance "$FQDN,$SQLPort" -Username "$SQLUserName" -Password "$SQLPwd" -As DataSet -TrustServerCertificate +$GetSQLServerVersionPath = (Join-Path $invocationDir "GetSQLServerVersion.sql" | Resolve-Path) +$DS = Invoke-Sqlcmd -InputFile $GetSQLServerVersionPath -ServerInstance "$FQDN,$SQLPort" -Username "$SQLUserName" -Password "$SQLPwd" -As DataSet -TrustServerCertificate $DS.Tables[0].Rows | %{ Log-Output "{ $($_['LoginName']), $($_['TimeStamp']), $($_['VersionDetail']), $($_['ProductVersion']), $($_['ProductLevel']), $($_['SqlVersion']), $($_['ServerCollation']) }" } $ActualSQLVersion = $DS.Tables[0].Rows[0]['SqlVersion']; diff --git a/CI/Azure-DevOps/GetSQLServerVersion.sql b/CI/Azure-DevOps/GetSQLServerVersion.sql index f61061063..242b2135c 100644 --- a/CI/Azure-DevOps/GetSQLServerVersion.sql +++ b/CI/Azure-DevOps/GetSQLServerVersion.sql @@ -13,6 +13,7 @@ WHEN '13.0' THEN '2016' WHEN '14.0' THEN '2017' WHEN '15.0' THEN '2019' + WHEN '16.0' THEN '2022' ELSE 'Unknown' END SQLVersion, SERVERPROPERTY('Collation') AS ServerCollation \ No newline at end of file diff --git a/NUL: b/NUL: deleted file mode 100644 index b9e5e6965..000000000 --- a/NUL: +++ /dev/null @@ -1 +0,0 @@ -Changed database context to 'tempdb'. diff --git a/tSQLtCLR/build.ps1 b/tSQLtCLR/Build.ps1 similarity index 59% rename from tSQLtCLR/build.ps1 rename to tSQLtCLR/Build.ps1 index 9e64efda7..a0ff38bfc 100644 --- a/tSQLtCLR/build.ps1 +++ b/tSQLtCLR/Build.ps1 @@ -1,3 +1,7 @@ +param( + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $pfxFilePath , + [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][securestring] $pfxPassword +) Push-Location -Path $PSScriptRoot # Create a unique temporary directory for this process @@ -9,10 +13,13 @@ try{ $snkFilePath = Join-Path -Path $tempDir -ChildPath "tSQLtOfficialSigningKey.snk" $pemFilePath = Join-Path -Path $tempDir -ChildPath "tSQLtOfficialSigningKey.pem" - $pfxFilePath = Join-Path -Path $env:TSQLTCERTPATH -ChildPath $("tSQLtOfficialSigningKey.pfx") - $pfxPassword = $env:TSQLTCERTPASSWORD - & openssl pkcs12 -in "$pfxFilePath" -out "$pemFilePath" -nodes -passin pass:$pfxPassword + + # $pfxPasswordCleartext = (ConvertFrom-SecureString $pfxPassword -AsPlainText); + $pfxPasswordCleartext = [System.Runtime.InteropServices.Marshal]::PtrToStringUni([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pfxPassword)); + & openssl pkcs12 -in "$pfxFilePath" -out "$pemFilePath" -nodes -passin pass:$pfxPasswordCleartext + & openssl pkcs12 -in "$pfxFilePath" -noout -info -nodes -passin pass:$pfxPasswordCleartext + Write-Warning("Certificate Thumbprint: " + (Get-PfxCertificate -Filepath "$pfxFilePath" -Password $pfxPassword).Thumbprint.ToString()); $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider $pemContent = Get-Content -Path "$pemFilePath" -Raw