diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 63062828b..45e42d111 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -52,8 +52,14 @@ fi echo "" echo "Generating SQL Server password..." SA_PASSWORD="$(openssl rand -base64 16 | tr -dc 'A-Za-z0-9' | head -c 16)Aa1!" -echo "$SA_PASSWORD" > /tmp/.sqlserver_sa_password -chmod 600 /tmp/.sqlserver_sa_password +# mktemp creates 0600; chmod before writing keeps it owner-only without touching the global umask. +SQL_ENV_FILE="$(mktemp)" +chmod 600 "$SQL_ENV_FILE" +trap 'rm -f "$SQL_ENV_FILE"' EXIT +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM +printf 'ACCEPT_EULA=Y\nMSSQL_SA_PASSWORD=%s\n' "$SA_PASSWORD" > "$SQL_ENV_FILE" # Start SQL Server container (use Azure SQL Edge for ARM64 compatibility) # This is optional - if Docker-in-Docker fails, the devcontainer still works @@ -63,15 +69,17 @@ echo "Starting SQL Server container (optional)..." ARCH=$(uname -m) if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then echo "Detected ARM64 - using Azure SQL Edge..." - docker run -e 'ACCEPT_EULA=Y' -e "MSSQL_SA_PASSWORD=$SA_PASSWORD" \ + docker run --env-file "$SQL_ENV_FILE" \ -p 1433:1433 --name sqlserver \ -d mcr.microsoft.com/azure-sql-edge:latest && SQL_STARTED=true || SQL_STARTED=false else echo "Detected x86_64 - using SQL Server 2025..." - docker run -e 'ACCEPT_EULA=Y' -e "MSSQL_SA_PASSWORD=$SA_PASSWORD" \ + docker run --env-file "$SQL_ENV_FILE" \ -p 1433:1433 --name sqlserver \ -d mcr.microsoft.com/mssql/server:2025-latest && SQL_STARTED=true || SQL_STARTED=false fi +rm -f "$SQL_ENV_FILE" +trap - EXIT HUP INT TERM if [ "$SQL_STARTED" = "true" ]; then echo "Waiting for SQL Server to start..." @@ -85,12 +93,18 @@ fi # Set DB_CONNECTION_STRING environment variable (persist across all terminals) DB_CONNECTION_STRING="Server=localhost,1433;Database=master;UID=sa;PWD=$SA_PASSWORD;TrustServerCertificate=Yes;Encrypt=Yes" -# Write to /etc/environment for system-wide persistence -echo "DB_CONNECTION_STRING=\"$DB_CONNECTION_STRING\"" | sudo tee -a /etc/environment > /dev/null - -# Also add to shell rc files for immediate availability in new terminals -echo "export DB_CONNECTION_STRING=\"$DB_CONNECTION_STRING\"" >> ~/.bashrc -echo "export DB_CONNECTION_STRING=\"$DB_CONNECTION_STRING\"" >> ~/.zshrc +# Keep the credential in a user-only file and source it from interactive shells. +MSSQL_ENV_FILE="$HOME/.mssql_python_env" +# Scope umask to this write only so the file is owner-only from creation; global umask stays untouched. +( umask 077; touch "$MSSQL_ENV_FILE"; chmod 600 "$MSSQL_ENV_FILE"; printf "export DB_CONNECTION_STRING='%s'\n" "$DB_CONNECTION_STRING" > "$MSSQL_ENV_FILE" ) + +# Remove plaintext entries left by older versions of this script. +[ -f /etc/environment ] && sudo sed -i '/^DB_CONNECTION_STRING=/d' /etc/environment +[ -f ~/.bashrc ] && sed -i '/^export DB_CONNECTION_STRING=/d' ~/.bashrc +[ -f ~/.zshrc ] && sed -i '/^export DB_CONNECTION_STRING=/d' ~/.zshrc +rm -f /tmp/.sqlserver_sa_password +grep -qxF 'source ~/.mssql_python_env' ~/.bashrc 2>/dev/null || echo 'source ~/.mssql_python_env' >> ~/.bashrc +grep -qxF 'source ~/.mssql_python_env' ~/.zshrc 2>/dev/null || echo 'source ~/.mssql_python_env' >> ~/.zshrc # Export for current session export DB_CONNECTION_STRING diff --git a/OneBranchPipelines/stages/build-linux-single-stage.yml b/OneBranchPipelines/stages/build-linux-single-stage.yml index 4293b90bc..74e3796f2 100644 --- a/OneBranchPipelines/stages/build-linux-single-stage.yml +++ b/OneBranchPipelines/stages/build-linux-single-stage.yml @@ -136,7 +136,7 @@ stages: displayName: 'Start $(LINUX_TAG) $(ARCH) container' - script: | - set -euxo pipefail + set -euo pipefail export PATH=$PATH:`pwd`/docker CONTAINER="build-$(LINUX_TAG)-$(ARCH)" @@ -170,7 +170,7 @@ stages: # manager is flaky under emulation (the header gate below is the real check). if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then docker exec "$CONTAINER" bash -lc ' - set -uxo pipefail + set -uo pipefail if command -v dnf >/dev/null 2>&1; then dnf -y install gcc gcc-c++ make cmake unixODBC-devel krb5-libs keyutils-libs ccache curl || true elif command -v yum >/dev/null 2>&1; then @@ -181,7 +181,7 @@ stages: ' else docker exec "$CONTAINER" sh -lc ' - set -uxo pipefail + set -uo pipefail apk add --no-cache bash build-base cmake unixodbc-dev krb5-libs keyutils-libs ccache curl || true gcc --version || true cmake --version || true @@ -199,20 +199,20 @@ stages: # Start SQL Server container for pytest execution # Runs on host (not in build container) to be accessible from build container via network - script: | - set -euxo pipefail + set -euo pipefail echo "Starting SQL Server 2022 container for testing..." docker run -d --name sqlserver-$(LINUX_TAG)-$(ARCH) \ --platform linux/amd64 \ -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ + -e MSSQL_SA_PASSWORD \ -p 1433:1433 \ mcr.microsoft.com/mssql/server:2022-latest echo "Waiting for SQL Server to be ready..." for i in {1..30}; do - if docker exec sqlserver-$(LINUX_TAG)-$(ARCH) /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -Q "SELECT 1" >/dev/null 2>&1; then + if docker exec -e SQLCMDPASSWORD sqlserver-$(LINUX_TAG)-$(ARCH) /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C -Q "SELECT 1" >/dev/null 2>&1; then echo "✓ SQL Server is ready!" break fi @@ -226,6 +226,8 @@ stages: displayName: 'Start SQL Server container for testing' env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) # ========================= # PHASE 2: EXTERNAL ODBC DRIVER PACKAGE @@ -256,7 +258,7 @@ stages: # Build wheels for all Python versions (3.10-3.14) and test each one - script: | - set -euxo pipefail + set -euo pipefail if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then SHELL_EXE=bash; else SHELL_EXE=sh; fi docker exec build-$(LINUX_TAG)-$(ARCH) $SHELL_EXE -lc 'mkdir -p /workspace/dist' @@ -269,8 +271,8 @@ stages: if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then # Manylinux (glibc-based) - use bash - docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" -e MANYLINUX_TAG="$(LINUX_TAG)" -e PIP_FIND_LINKS="/workspace/odbc_wheels" build-$(LINUX_TAG)-$(ARCH) bash -lc ' - set -euxo pipefail; + docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD -e MANYLINUX_TAG="$(LINUX_TAG)" -e PIP_FIND_LINKS="/workspace/odbc_wheels" build-$(LINUX_TAG)-$(ARCH) bash -lc ' + set -euo pipefail; # Step 1: Setup Python environment PY=/opt/python/${PYBIN}-${PYBIN}/bin/python; @@ -339,8 +341,8 @@ stages: ' else # Musllinux (musl libc-based) - use sh - docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" -e MANYLINUX_TAG="$(LINUX_TAG)" -e PIP_FIND_LINKS="/workspace/odbc_wheels" build-$(LINUX_TAG)-$(ARCH) sh -lc ' - set -euxo pipefail; + docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD -e MANYLINUX_TAG="$(LINUX_TAG)" -e PIP_FIND_LINKS="/workspace/odbc_wheels" build-$(LINUX_TAG)-$(ARCH) sh -lc ' + set -euo pipefail; # Step 1: Setup Python environment PY=/opt/python/${PYBIN}-${PYBIN}/bin/python; @@ -422,7 +424,7 @@ stages: # Copy built artifacts from container to host for publishing - script: | - set -euxo pipefail + set -euo pipefail # Copy all wheels (5 Python versions) to output directory echo "Copying wheels to host..." diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index 5f665ee84..9d94245f5 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -161,20 +161,22 @@ stages: docker pull mcr.microsoft.com/mssql/server:2022-latest docker run --name sqlserver \ -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="${DB_PASSWORD}" \ + -e MSSQL_SA_PASSWORD \ -p 1433:1433 -d \ mcr.microsoft.com/mssql/server:2022-latest # Wait for SQL Server to accept connections (up to 60 seconds) # sqlcmd -C flag = trust server certificate (for TLS connection) for i in {1..30}; do - docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$DB_PASSWORD" -C -Q "SELECT 1" && break + docker exec -e SQLCMDPASSWORD sqlserver /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C -Q "SELECT 1" && break sleep 2 done displayName: 'Start SQL Server (Docker)' env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) # ========================= # MSSQL-PYTHON-RS INSTALLATION diff --git a/OneBranchPipelines/stages/build-windows-single-stage.yml b/OneBranchPipelines/stages/build-windows-single-stage.yml index 086d09091..a81e045db 100644 --- a/OneBranchPipelines/stages/build-windows-single-stage.yml +++ b/OneBranchPipelines/stages/build-windows-single-stage.yml @@ -168,7 +168,20 @@ stages: - powershell: | sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE TestDB" - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" + $sqlPassword = $env:DB_PASSWORD.Replace("'", "''") + $sqlPath = Join-Path $env:TEMP ("create-testuser-{0}.sql" -f [guid]::NewGuid()) + $sqlBytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes("CREATE LOGIN testuser WITH PASSWORD = '$sqlPassword'`n") + [System.IO.File]::Create($sqlPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $sqlPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure temporary SQL file" } + [System.IO.File]::WriteAllBytes($sqlPath, $sqlBytes) + sqlcmd -S "(localdb)\MSSQLLocalDB" -i $sqlPath -b -x -f 65001 + if ($LASTEXITCODE -ne 0) { throw "Failed to create the testuser login" } + } finally { + Remove-Item $sqlPath -Force -ErrorAction Stop + } sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" displayName: 'Setup database and user' diff --git a/OneBranchPipelines/stress-test-pipeline.yml b/OneBranchPipelines/stress-test-pipeline.yml index 2d4b22ad3..548f52186 100644 --- a/OneBranchPipelines/stress-test-pipeline.yml +++ b/OneBranchPipelines/stress-test-pipeline.yml @@ -107,7 +107,20 @@ jobs: - powershell: | Write-Host "Creating database and user for stress tests..." sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE StressTestDB" - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$env:DB_PASSWORD'" + $sqlPassword = $env:DB_PASSWORD.Replace("'", "''") + $sqlPath = Join-Path $env:TEMP ("create-testuser-{0}.sql" -f [guid]::NewGuid()) + $sqlBytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes("CREATE LOGIN testuser WITH PASSWORD = '$sqlPassword'`n") + [System.IO.File]::Create($sqlPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $sqlPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure temporary SQL file" } + [System.IO.File]::WriteAllBytes($sqlPath, $sqlBytes) + sqlcmd -S "(localdb)\MSSQLLocalDB" -i $sqlPath -b -x -f 65001 + if ($LASTEXITCODE -ne 0) { throw "Failed to create the testuser login" } + } finally { + Remove-Item $sqlPath -Force -ErrorAction Stop + } sqlcmd -S "(localdb)\MSSQLLocalDB" -d StressTestDB -Q "CREATE USER testuser FOR LOGIN testuser" sqlcmd -S "(localdb)\MSSQLLocalDB" -d StressTestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" Write-Host "Database setup completed" @@ -117,7 +130,7 @@ jobs: # Run multi-threaded stress tests in an isolated subprocess. - powershell: | - $env:DB_CONNECTION_STRING = 'Server=(localdb)\MSSQLLocalDB;Database=StressTestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + $env:DB_CONNECTION_STRING = "Server=(localdb)\MSSQLLocalDB;Database=StressTestDB;Uid=testuser;Pwd=$env:DB_PASSWORD;TrustServerCertificate=yes" Write-Host "Starting stress tests..." $xmlFile = "stress-test-results.xml" @@ -158,17 +171,21 @@ jobs: Write-Host "Test failures detected or XML not found" exit 1 displayName: 'Run Multi-Threaded Stress Tests' + env: + DB_PASSWORD: $(DB_PASSWORD) continueOnError: true # Run original stress tests - powershell: | - $env:DB_CONNECTION_STRING = 'Server=(localdb)\MSSQLLocalDB;Database=StressTestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + $env:DB_CONNECTION_STRING = "Server=(localdb)\MSSQLLocalDB;Database=StressTestDB;Uid=testuser;Pwd=$env:DB_PASSWORD;TrustServerCertificate=yes" python -m pytest tests/test_011_singlethreaded_stress.py -v -m "stress" --junitxml=perf-stress-test-results.xml --timeout=1800 --capture=tee-sys $exitCode = $LASTEXITCODE Write-Host "Pytest exit code: $exitCode" exit $exitCode displayName: 'Run Single-Threaded Stress Tests' + env: + DB_PASSWORD: $(DB_PASSWORD) continueOnError: true # Publish test results @@ -265,20 +282,20 @@ jobs: # Start SQL Server container for stress tests - script: | - set -euxo pipefail + set -euo pipefail echo "Starting SQL Server 2022 container for stress tests..." docker run -d --name sqlserver-stress \ --platform linux/amd64 \ -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ + -e MSSQL_SA_PASSWORD \ -p 1433:1433 \ mcr.microsoft.com/mssql/server:2022-latest echo "Waiting for SQL Server to be ready..." for i in {1..30}; do - if docker exec sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -Q "SELECT 1" >/dev/null 2>&1; then + if docker exec -e SQLCMDPASSWORD sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C -Q "SELECT 1" >/dev/null 2>&1; then echo "SQL Server is ready!" break fi @@ -287,19 +304,22 @@ jobs: done echo "Creating database and user for stress tests..." - docker exec sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -Q "CREATE DATABASE StressTestDB" - docker exec sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" - docker exec sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -d StressTestDB -Q "CREATE USER testuser FOR LOGIN testuser" - docker exec sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -d StressTestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" + docker exec -e SQLCMDPASSWORD sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C -Q "CREATE DATABASE StressTestDB" + SQL_PASSWORD=${DB_PASSWORD//\'/\'\'} + printf "CREATE LOGIN testuser WITH PASSWORD = '%s'\n" "$SQL_PASSWORD" | \ + docker exec -i -e SQLCMDPASSWORD sqlserver-stress /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -C -b -x + docker exec -e SQLCMDPASSWORD sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C -d StressTestDB -Q "CREATE USER testuser FOR LOGIN testuser" + docker exec -e SQLCMDPASSWORD sqlserver-stress /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C -d StressTestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" echo "Database setup completed" displayName: 'Start SQL Server container and setup database' env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) # Run multi-threaded stress tests in an isolated subprocess. - script: | diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 6e5038d89..46ca6a61c 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -92,7 +92,20 @@ jobs: # Create database and user for LocalDB - powershell: | sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE TestDB" - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" + $sqlPassword = $env:DB_PASSWORD.Replace("'", "''") + $sqlPath = Join-Path $env:TEMP ("create-testuser-{0}.sql" -f [guid]::NewGuid()) + $sqlBytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes("CREATE LOGIN testuser WITH PASSWORD = '$sqlPassword'`n") + [System.IO.File]::Create($sqlPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $sqlPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure temporary SQL file" } + [System.IO.File]::WriteAllBytes($sqlPath, $sqlBytes) + sqlcmd -S "(localdb)\MSSQLLocalDB" -i $sqlPath -b -x -f 65001 + if ($LASTEXITCODE -ne 0) { throw "Failed to create the testuser login" } + } finally { + Remove-Item $sqlPath -Force -ErrorAction Stop + } sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" displayName: 'Setup database and user for LocalDB' @@ -119,7 +132,30 @@ jobs: Start-Process -FilePath $setupFile.FullName -ArgumentList "/x:$env:TEMP\SQLSetup","/u" -Wait Write-Host "Running SQL Server setup..." - Start-Process -FilePath "$env:TEMP\SQLSetup\setup.exe" -ArgumentList "/Q","/ACTION=Install","/FEATURES=SQLEngine","/INSTANCENAME=MSSQLSERVER","/SQLSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"","/SQLSYSADMINACCOUNTS=`"BUILTIN\Administrators`"","/TCPENABLED=1","/SECURITYMODE=SQL","/SAPWD=$(DB_PASSWORD)","/IACCEPTSQLSERVERLICENSETERMS" -Wait + $configPath = Join-Path $env:TEMP ("sql2022-setup-{0}.ini" -f [guid]::NewGuid()) + $configLines = @( + '[OPTIONS]' + 'QUIET="True"' + 'ACTION="Install"' + 'FEATURES=SQLEngine' + 'INSTANCENAME=MSSQLSERVER' + 'SQLSVCACCOUNT="NT AUTHORITY\SYSTEM"' + 'SQLSYSADMINACCOUNTS="BUILTIN\Administrators"' + 'TCPENABLED="1"' + 'SECURITYMODE="SQL"' + "SAPWD=`"$env:DB_PASSWORD`"" + 'IACCEPTSQLSERVERLICENSETERMS="True"' + ) + [System.IO.File]::Create($configPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $configPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure SQL Server setup configuration" } + Set-Content -Path $configPath -Value $configLines -Encoding ASCII + Start-Process -FilePath "$env:TEMP\SQLSetup\setup.exe" -ArgumentList "/ConfigurationFile=`"$configPath`"" -Wait + } finally { + Remove-Item $configPath -Force -ErrorAction Stop + } } else { Write-Error "Failed to download SQL Server setup file" exit 1 @@ -133,6 +169,7 @@ jobs: # Create database for SQL Server 2022 - powershell: | + $env:SQLCMDPASSWORD = $env:DB_PASSWORD # Wait for SQL Server to start $maxAttempts = 30 $attempt = 0 @@ -140,11 +177,11 @@ jobs: Write-Host "Waiting for SQL Server 2022 to start..." while (-not $connected -and $attempt -lt $maxAttempts) { - try { - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SELECT 1" -C + sqlcmd -S "localhost" -U "sa" -Q "SELECT 1" -C -b + if ($LASTEXITCODE -eq 0) { $connected = $true Write-Host "SQL Server is ready!" - } catch { + } else { $attempt++ Write-Host "Waiting... ($attempt/$maxAttempts)" Start-Sleep -Seconds 2 @@ -157,12 +194,25 @@ jobs: } # Create database and user - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE DATABASE TestDB" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" -C + sqlcmd -S "localhost" -U "sa" -Q "CREATE DATABASE TestDB" -C + $sqlPassword = $env:DB_PASSWORD.Replace("'", "''") + $sqlPath = Join-Path $env:TEMP ("create-testuser-{0}.sql" -f [guid]::NewGuid()) + $sqlBytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes("CREATE LOGIN testuser WITH PASSWORD = '$sqlPassword'`n") + [System.IO.File]::Create($sqlPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $sqlPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure temporary SQL file" } + [System.IO.File]::WriteAllBytes($sqlPath, $sqlBytes) + sqlcmd -S "localhost" -U "sa" -C -i $sqlPath -b -x -f 65001 + if ($LASTEXITCODE -ne 0) { throw "Failed to create the testuser login" } + } finally { + Remove-Item $sqlPath -Force -ErrorAction Stop + } + sqlcmd -S "localhost" -U "sa" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" -C + sqlcmd -S "localhost" -U "sa" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" -C # Pool eviction tests observe physical connections from a second session. - sqlcmd -S "localhost" -U "sa" -P "$env:DB_PASSWORD" -b -Q "GRANT VIEW SERVER PERFORMANCE STATE TO [testuser]; EXECUTE AS LOGIN = 'testuser'; SELECT connection_id FROM sys.dm_exec_connections WHERE session_id = @@SPID; REVERT;" -C + sqlcmd -S "localhost" -U "sa" -b -Q "GRANT VIEW SERVER PERFORMANCE STATE TO [testuser]; EXECUTE AS LOGIN = 'testuser'; SELECT connection_id FROM sys.dm_exec_connections WHERE session_id = @@SPID; REVERT;" -C if ($LASTEXITCODE -ne 0) { throw "Failed to provision the CI test login's DMV-read permission." } displayName: 'Setup database and user for SQL Server 2022' condition: eq(variables['sqlVersion'], 'SQL2022') @@ -188,7 +238,30 @@ jobs: Start-Process -FilePath $setupFile.FullName -ArgumentList "/x:$env:TEMP\SQL2025Setup","/u" -Wait Write-Host "Running SQL Server setup..." - Start-Process -FilePath "$env:TEMP\SQL2025Setup\setup.exe" -ArgumentList "/Q","/ACTION=Install","/FEATURES=SQLEngine","/INSTANCENAME=MSSQLSERVER","/SQLSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"","/SQLSYSADMINACCOUNTS=`"BUILTIN\Administrators`"","/TCPENABLED=1","/SECURITYMODE=SQL","/SAPWD=$(DB_PASSWORD)","/IACCEPTSQLSERVERLICENSETERMS" -Wait + $configPath = Join-Path $env:TEMP ("sql2025-setup-{0}.ini" -f [guid]::NewGuid()) + $configLines = @( + '[OPTIONS]' + 'QUIET="True"' + 'ACTION="Install"' + 'FEATURES=SQLEngine' + 'INSTANCENAME=MSSQLSERVER' + 'SQLSVCACCOUNT="NT AUTHORITY\SYSTEM"' + 'SQLSYSADMINACCOUNTS="BUILTIN\Administrators"' + 'TCPENABLED="1"' + 'SECURITYMODE="SQL"' + "SAPWD=`"$env:DB_PASSWORD`"" + 'IACCEPTSQLSERVERLICENSETERMS="True"' + ) + [System.IO.File]::Create($configPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $configPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure SQL Server setup configuration" } + Set-Content -Path $configPath -Value $configLines -Encoding ASCII + Start-Process -FilePath "$env:TEMP\SQL2025Setup\setup.exe" -ArgumentList "/ConfigurationFile=`"$configPath`"" -Wait + } finally { + Remove-Item $configPath -Force -ErrorAction Stop + } } else { Write-Error "Failed to download SQL Server setup file" exit 1 @@ -202,6 +275,7 @@ jobs: # Create database for SQL Server 2025 - powershell: | + $env:SQLCMDPASSWORD = $env:DB_PASSWORD # Wait for SQL Server to start $maxAttempts = 30 $attempt = 0 @@ -209,11 +283,11 @@ jobs: Write-Host "Waiting for SQL Server 2025 to start..." while (-not $connected -and $attempt -lt $maxAttempts) { - try { - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SELECT 1" -C + sqlcmd -S "localhost" -U "sa" -Q "SELECT 1" -C -b + if ($LASTEXITCODE -eq 0) { $connected = $true Write-Host "SQL Server is ready!" - } catch { + } else { $attempt++ Write-Host "Waiting... ($attempt/$maxAttempts)" Start-Sleep -Seconds 2 @@ -226,12 +300,25 @@ jobs: } # Create database and user - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE DATABASE TestDB" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" -C + sqlcmd -S "localhost" -U "sa" -Q "CREATE DATABASE TestDB" -C + $sqlPassword = $env:DB_PASSWORD.Replace("'", "''") + $sqlPath = Join-Path $env:TEMP ("create-testuser-{0}.sql" -f [guid]::NewGuid()) + $sqlBytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes("CREATE LOGIN testuser WITH PASSWORD = '$sqlPassword'`n") + [System.IO.File]::Create($sqlPath).Dispose() + try { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + icacls $sqlPath /inheritance:r /grant:r "${identity}:(F)" "SYSTEM:(R)" | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to secure temporary SQL file" } + [System.IO.File]::WriteAllBytes($sqlPath, $sqlBytes) + sqlcmd -S "localhost" -U "sa" -C -i $sqlPath -b -x -f 65001 + if ($LASTEXITCODE -ne 0) { throw "Failed to create the testuser login" } + } finally { + Remove-Item $sqlPath -Force -ErrorAction Stop + } + sqlcmd -S "localhost" -U "sa" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" -C + sqlcmd -S "localhost" -U "sa" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" -C # Pool eviction tests observe physical connections from a second session. - sqlcmd -S "localhost" -U "sa" -P "$env:DB_PASSWORD" -b -Q "GRANT VIEW SERVER PERFORMANCE STATE TO [testuser]; EXECUTE AS LOGIN = 'testuser'; SELECT connection_id FROM sys.dm_exec_connections WHERE session_id = @@SPID; REVERT;" -C + sqlcmd -S "localhost" -U "sa" -b -Q "GRANT VIEW SERVER PERFORMANCE STATE TO [testuser]; EXECUTE AS LOGIN = 'testuser'; SELECT connection_id FROM sys.dm_exec_connections WHERE session_id = @@SPID; REVERT;" -C if ($LASTEXITCODE -ne 0) { throw "Failed to provision the CI test login's DMV-read permission." } displayName: 'Setup database and user for SQL Server 2025' condition: eq(variables['sqlVersion'], 'SQL2025') @@ -286,14 +373,15 @@ jobs: # Download and restore AdventureWorks2022 database for benchmarking - powershell: | + $env:SQLCMDPASSWORD = $env:DB_PASSWORD Write-Host "Downloading AdventureWorks2022.bak..." $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri "https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2022.bak" -OutFile "$env:TEMP\AdventureWorks2022.bak" Write-Host "Restoring AdventureWorks2022 database..." # Get the default data and log paths - $dataPath = sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SET NOCOUNT ON; SELECT SERVERPROPERTY('InstanceDefaultDataPath') AS DataPath" -h -1 -C | Out-String - $logPath = sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SET NOCOUNT ON; SELECT SERVERPROPERTY('InstanceDefaultLogPath') AS LogPath" -h -1 -C | Out-String + $dataPath = sqlcmd -S "localhost" -U "sa" -Q "SET NOCOUNT ON; SELECT SERVERPROPERTY('InstanceDefaultDataPath') AS DataPath" -h -1 -C | Out-String + $logPath = sqlcmd -S "localhost" -U "sa" -Q "SET NOCOUNT ON; SELECT SERVERPROPERTY('InstanceDefaultLogPath') AS LogPath" -h -1 -C | Out-String $dataPath = $dataPath.Trim() $logPath = $logPath.Trim() @@ -302,7 +390,7 @@ jobs: Write-Host "Log path: $logPath" # Restore the database - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -C -Q @" + sqlcmd -S "localhost" -U "sa" -C -Q @" RESTORE DATABASE AdventureWorks2022 FROM DISK = '$env:TEMP\AdventureWorks2022.bak' WITH @@ -584,6 +672,8 @@ jobs: displayName: 'Build + start SQL Server (Colima boot & SQL setup overlapped with build)' env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: python -m eng.profiler_benchmarks.controller --check-build on displayName: 'Verify native configuration and recording OFF before pytest' @@ -647,8 +737,8 @@ jobs: docker cp /tmp/AdventureWorks2022.bak sqlserver:/tmp/AdventureWorks2022.bak echo "Restoring AdventureWorks2022 database..." - docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$DB_PASSWORD" -C \ + docker exec -e SQLCMDPASSWORD sqlserver /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -C \ -Q "RESTORE DATABASE AdventureWorks2022 FROM DISK = '/tmp/AdventureWorks2022.bak' WITH MOVE 'AdventureWorks2022' TO '/var/opt/mssql/data/AdventureWorks2022.mdf', MOVE 'AdventureWorks2022_log' TO '/var/opt/mssql/data/AdventureWorks2022_log.ldf', REPLACE" if [ $? -eq 0 ]; then @@ -664,6 +754,7 @@ jobs: continueOnError: true env: DB_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) # Run performance benchmarks on macOS - script: | @@ -758,6 +849,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install dependencies in the container @@ -884,7 +977,7 @@ jobs: echo "Testing against Azure SQL Database" docker exec \ - -e DB_CONNECTION_STRING="$(AZURE_CONNECTION_STRING)" \ + -e DB_CONNECTION_STRING \ test-container-$(distroName) bash -c " source /opt/venv/bin/activate echo 'Build successful, running tests now on $(distroName) with Azure SQL' @@ -895,10 +988,11 @@ jobs: # Local SQL Server testing export SQLSERVER_IP=$(docker inspect sqlserver-$(distroName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ + -e DB_CONNECTION_STRING \ + -e DB_PASSWORD \ test-container-$(distroName) bash -c " source /opt/venv/bin/activate echo 'Build successful, running tests now on $(distroName)' @@ -910,6 +1004,7 @@ jobs: condition: and(succeeded(), or(eq(variables['useAzureSQL'], 'false'), and(eq(variables['useAzureSQL'], 'true'), ne(variables['AZURE_CONNECTION_STRING'], '')))) env: DB_PASSWORD: $(DB_PASSWORD) + DB_CONNECTION_STRING: $(AZURE_CONNECTION_STRING) - script: | # Download and restore AdventureWorks2022 database for benchmarking on Ubuntu only @@ -921,10 +1016,9 @@ jobs: docker cp /tmp/AdventureWorks2022.bak sqlserver-$(distroName):/tmp/AdventureWorks2022.bak echo "Restoring AdventureWorks2022 database..." - docker exec sqlserver-$(distroName) /opt/mssql-tools18/bin/sqlcmd \ + docker exec -e SQLCMDPASSWORD sqlserver-$(distroName) /opt/mssql-tools18/bin/sqlcmd \ -S localhost \ -U SA \ - -P "$(DB_PASSWORD)" \ -C \ -Q "RESTORE DATABASE AdventureWorks2022 FROM DISK = '/tmp/AdventureWorks2022.bak' WITH MOVE 'AdventureWorks2022' TO '/var/opt/mssql/data/AdventureWorks2022.mdf', MOVE 'AdventureWorks2022_log' TO '/var/opt/mssql/data/AdventureWorks2022_log.ldf', REPLACE" @@ -943,12 +1037,14 @@ jobs: continueOnError: true env: DB_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Run performance benchmarks on Ubuntu with SQL Server 2022 only if [ "$(distroName)" = "Ubuntu" ] && [ "$(useAzureSQL)" = "false" ]; then export SQLSERVER_IP=$(docker inspect sqlserver-$(distroName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "Running performance benchmarks on Ubuntu with SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=AdventureWorks2022;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ -e BUILD_BUILDID \ @@ -1068,6 +1164,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install dependencies in the ARM64 container @@ -1209,10 +1307,11 @@ jobs: # Get SQL Server container IP SQLSERVER_IP=$(docker inspect sqlserver-$(distroName)-$(archName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ + -e DB_CONNECTION_STRING \ + -e DB_PASSWORD \ test-container-$(distroName)-$(archName) bash -c " source /opt/venv/bin/activate echo 'Build successful, running tests now on $(distroName) ARM64' @@ -1277,6 +1376,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install dependencies in the RHEL 9 container @@ -1408,10 +1509,11 @@ jobs: # Get SQL Server container IP SQLSERVER_IP=$(docker inspect sqlserver-rhel9 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ + -e DB_CONNECTION_STRING \ + -e DB_PASSWORD \ test-container-rhel9 bash -c " source myvenv/bin/activate echo 'Build successful, running tests now on RHEL 9' @@ -1487,6 +1589,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install dependencies in the RHEL 9 ARM64 container @@ -1621,10 +1725,11 @@ jobs: # Get SQL Server container IP SQLSERVER_IP=$(docker inspect sqlserver-rhel9-arm64 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ + -e DB_CONNECTION_STRING \ + -e DB_PASSWORD \ test-container-rhel9-arm64 bash -c " source myvenv/bin/activate echo 'Build successful, running tests now on RHEL 9 ARM64' @@ -1696,6 +1801,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install dependencies in the Alpine x86_64 container @@ -1840,10 +1947,11 @@ jobs: # Get SQL Server container IP SQLSERVER_IP=$(docker inspect sqlserver-alpine --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ + -e DB_CONNECTION_STRING \ + -e DB_PASSWORD \ test-container-alpine bash -c " echo 'Build successful, running tests now on Alpine x86_64' echo 'Architecture:' \$(uname -m) @@ -1935,6 +2043,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install dependencies in the Alpine ARM64 container @@ -2079,10 +2189,11 @@ jobs: # Get SQL Server container IP SQLSERVER_IP=$(docker inspect sqlserver-alpine-arm64 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') echo "SQL Server IP: $SQLSERVER_IP" + export DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ + -e DB_CONNECTION_STRING \ + -e DB_PASSWORD \ test-container-alpine-arm64 bash -c " echo 'Build successful, running tests now on Alpine ARM64' echo 'Architecture:' \$(uname -m) @@ -2334,7 +2445,7 @@ jobs: # # - script: | # docker exec \ -# -e DB_CONNECTION_STRING="$(AZURE_CONNECTION_STRING)" \ +# -e DB_CONNECTION_STRING \ # test-container-ubuntu-azuresql bash -c " # source /opt/venv/bin/activate # echo 'Running tests on Ubuntu against Azure SQL Database' @@ -2382,6 +2493,8 @@ jobs: timeoutInMinutes: 22 env: DB_PASSWORD: $(DB_PASSWORD) + MSSQL_SA_PASSWORD: $(DB_PASSWORD) + SQLCMDPASSWORD: $(DB_PASSWORD) - script: | # Install Python dependencies