Skip to content

Commit eb1f9a8

Browse files
authored
Merge pull request #276 from Azure/dev/benjin/validateDocker
Performing validation with a local docker instance instead of Azure DB
2 parents 7634c8a + c656f59 commit eb1f9a8

3 files changed

Lines changed: 197 additions & 110 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: 'Set up SQL Server on Linux'
2+
description: 'Starts a SQL Server 2022 Docker container with SA auth and exports BASE_CS.'
3+
4+
inputs:
5+
sa-password:
6+
description: 'SA password to use for the SQL Server instance.'
7+
required: true
8+
image:
9+
description: 'SQL Server Docker image to use.'
10+
required: false
11+
default: 'mcr.microsoft.com/mssql/server:2025-latest'
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Start SQL Server container
17+
shell: bash
18+
env:
19+
SA_PASSWORD: ${{ inputs.sa-password }}
20+
MSSQL_IMAGE: ${{ inputs.image }}
21+
run: |
22+
docker run -d --name sqlserver \
23+
-e "ACCEPT_EULA=Y" \
24+
-e "MSSQL_SA_PASSWORD=${SA_PASSWORD}" \
25+
-p 1433:1433 \
26+
"$MSSQL_IMAGE"
27+
28+
- name: Wait for SQL Server to be ready
29+
shell: bash
30+
env:
31+
SA_PASSWORD: ${{ inputs.sa-password }}
32+
run: |
33+
for i in $(seq 1 30); do
34+
if docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \
35+
-S localhost -U sa -P "${SA_PASSWORD}" -C -Q 'SELECT 1' >/dev/null 2>&1; then
36+
echo "SQL Server is ready"
37+
exit 0
38+
fi
39+
echo "Waiting for SQL Server... ($i/30)"
40+
sleep 5
41+
done
42+
echo "SQL Server did not become ready in time"
43+
docker logs sqlserver
44+
exit 1
45+
46+
- name: Set connection string
47+
shell: bash
48+
env:
49+
SA_PASSWORD: ${{ inputs.sa-password }}
50+
run: |
51+
echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV"
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: 'Set up SQL Server on Windows'
2+
description: 'Installs SQL Server 2025 Express with SA auth and exports BASE_CS.'
3+
4+
inputs:
5+
sa-password:
6+
description: 'SA password to use for the SQL Server instance.'
7+
required: true
8+
9+
runs:
10+
using: composite
11+
steps:
12+
- name: Download SQL Server 2025 Express installer
13+
shell: pwsh
14+
run: |
15+
$ssei = Join-Path $env:RUNNER_TEMP 'SQL2025-SSEI-Expr.exe'
16+
$media = Join-Path $env:RUNNER_TEMP 'sql-media'
17+
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?linkid=2216019' -OutFile $ssei
18+
Write-Host "Downloaded SSEI: $((Get-Item $ssei).Length) bytes"
19+
20+
$p = Start-Process -FilePath $ssei -Wait -PassThru -ArgumentList @(
21+
'/Quiet',
22+
'/Action=Download',
23+
'/Language=en-US',
24+
"/MediaPath=$media",
25+
'/MediaType=Core',
26+
'/HideProgressBar'
27+
)
28+
if ($p.ExitCode -ne 0) { throw "SSEI download failed with exit code $($p.ExitCode)" }
29+
30+
Write-Host "=== Media directory contents ==="
31+
Get-ChildItem $media
32+
33+
- name: Install SQL Server Express with SA auth
34+
shell: pwsh
35+
env:
36+
SA_PASSWORD: ${{ inputs.sa-password }}
37+
run: |
38+
$media = Join-Path $env:RUNNER_TEMP 'sql-media'
39+
$selfExtract = Get-ChildItem $media -Filter 'SQLEXPR*.exe' | Select-Object -First 1
40+
if (-not $selfExtract) { throw "Installer EXE not found in $media" }
41+
42+
$extracted = Join-Path $env:RUNNER_TEMP 'sql-extracted'
43+
Write-Host "Extracting $($selfExtract.FullName) -> $extracted"
44+
$extract = Start-Process -FilePath $selfExtract.FullName -Wait -PassThru `
45+
-ArgumentList '/Q', "/X:$extracted"
46+
if ($extract.ExitCode -ne 0) { throw "Extraction failed with exit code $($extract.ExitCode)" }
47+
48+
$setup = Join-Path $extracted 'setup.exe'
49+
if (-not (Test-Path $setup)) { throw "setup.exe not found at $setup" }
50+
51+
Write-Host "Running $setup with SECURITYMODE=SQL"
52+
$install = Start-Process -FilePath $setup -Wait -PassThru -ArgumentList @(
53+
'/Q',
54+
'/ACTION=Install',
55+
'/FEATURES=SQLEngine',
56+
'/INSTANCENAME=MSSQLSERVER',
57+
'/SECURITYMODE=SQL',
58+
"/SAPWD=$env:SA_PASSWORD",
59+
'/TCPENABLED=1',
60+
'/IACCEPTSQLSERVERLICENSETERMS',
61+
'/UPDATEENABLED=False',
62+
'/SQLSYSADMINACCOUNTS=BUILTIN\Administrators'
63+
)
64+
65+
if ($install.ExitCode -ne 0) {
66+
Write-Host "Setup failed with exit code $($install.ExitCode)"
67+
$logRoot = 'C:\Program Files\Microsoft SQL Server'
68+
if (Test-Path $logRoot) {
69+
Get-ChildItem $logRoot -Recurse -Filter 'Summary*.txt' -ErrorAction SilentlyContinue |
70+
Sort-Object LastWriteTime -Descending | Select-Object -First 1 |
71+
ForEach-Object { Write-Host "=== $($_.FullName) ==="; Get-Content $_.FullName }
72+
}
73+
throw "SQL Server install failed"
74+
}
75+
Get-Service | Where-Object { $_.Name -like 'MSSQL*' } | Format-Table
76+
77+
- name: Verify SQL auth
78+
shell: pwsh
79+
env:
80+
SA_PASSWORD: ${{ inputs.sa-password }}
81+
run: |
82+
$sqlcmd = (Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinue |
83+
Select-Object -First 1).FullName
84+
if (-not $sqlcmd) { throw "sqlcmd.exe not found after install" }
85+
& $sqlcmd -S 'localhost' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION"
86+
if ($LASTEXITCODE -ne 0) { throw "SQL auth verification failed" }
87+
88+
- name: Set connection string
89+
shell: bash
90+
env:
91+
SA_PASSWORD: ${{ inputs.sa-password }}
92+
run: |
93+
echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV"

.github/workflows/pr-check.yml

Lines changed: 53 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,105 @@
11
name: pr-check
22

3-
# Note: If you need to make changes to this file, please use a branch off the main branch instead of a fork.
4-
# The pull_request target from a forked repo will not have access to the secrets needed for this workflow.
3+
# Tests PR code against a local SQL Server instance so no Azure credentials are required.
4+
# This workflow uses the pull_request trigger (not pull_request_target), so fork PRs run
5+
# with no secrets and no elevated permissions.
6+
#
7+
# - Linux runners: spin up SQL Server 2022 in a Docker container with SA auth.
8+
# - Windows runners: install SQL Server 2025 Express directly from Microsoft with SA auth.
59

610
on:
7-
pull_request_target:
811
pull_request:
9-
paths:
10-
- '.github/workflows/pr-check.yml'
1112

1213
permissions: {}
1314

1415
jobs:
15-
# Build job that safely builds artifacts from PR code without access to secrets
16-
build:
17-
environment: Automation test # Require approval before running the action
16+
test:
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
os: [ubuntu-latest, windows-latest]
1821
runs-on: ${{ matrix.os }}
1922
permissions:
2023
contents: read
21-
strategy:
22-
matrix:
23-
os: [windows-latest, ubuntu-latest]
24+
checks: write
25+
26+
env:
27+
TEST_DB: SqlActionTest
28+
29+
defaults:
30+
run:
31+
shell: bash
32+
2433
steps:
25-
- name: Checkout from PR branch
34+
- name: Checkout PR
2635
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2736
with:
28-
repository: ${{ github.event.pull_request.head.repo.full_name }}
2937
ref: ${{ github.event.pull_request.head.sha }}
3038

31-
- name: Verify package-lock.json exists
39+
- name: Generate SA password
3240
run: |
33-
if (!(Test-Path package-lock.json)) {
34-
Write-Error "package-lock.json not found. Please commit package-lock.json to ensure reproducible builds."
35-
exit 1
36-
}
37-
shell: pwsh
41+
SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!"
42+
echo "::add-mask::${SA_PASSWORD}"
43+
echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV"
3844
39-
- name: Check if package-lock.json was modified
40-
run: |
41-
# Check git log to see if package-lock.json was modified in this PR
42-
git fetch origin ${{ github.base_ref }} --depth=1
43-
$changedFiles = git diff --name-only origin/${{ github.base_ref }}...HEAD
44-
45-
if ($changedFiles -match "package-lock.json") {
46-
Write-Warning "⚠️ package-lock.json has been modified in this PR."
47-
Write-Warning "This requires manual review to ensure no malicious dependencies were added."
48-
Write-Warning "Reviewers: Please carefully examine the dependency changes before approving."
49-
} else {
50-
Write-Host "✓ package-lock.json unchanged - no new dependencies" -ForegroundColor Green
51-
}
52-
shell: pwsh
53-
continue-on-error: true
54-
55-
- name: Verify package.json integrity
56-
run: |
57-
# Check for suspicious scripts that could be used for attacks
58-
$packageJson = Get-Content package.json | ConvertFrom-Json
59-
$suspiciousScripts = @('preinstall', 'postinstall', 'prepack', 'postpack')
60-
61-
foreach ($script in $suspiciousScripts) {
62-
if ($packageJson.scripts.$script) {
63-
Write-Warning "⚠️ Found lifecycle script '$script' in package.json"
64-
Write-Warning "Script content: $($packageJson.scripts.$script)"
65-
Write-Warning "Reviewers: Please verify this script is legitimate"
66-
}
67-
}
68-
shell: pwsh
69-
70-
- name: Installing node_modules with ci (uses lockfile, ignores scripts)
71-
run: npm ci --ignore-scripts
72-
73-
- name: Audit dependencies for known vulnerabilities
74-
run: npm audit --audit-level=high
75-
continue-on-error: true
76-
77-
- name: Build GitHub Action
78-
run: npm run build
79-
80-
- name: Upload build artifact
81-
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
45+
- name: Set up SQL Server (Linux)
46+
if: runner.os == 'Linux'
47+
uses: ./.github/actions/setup-sql-linux
8248
with:
83-
name: action-build-${{ matrix.os }}
84-
path: |
85-
lib/
86-
node_modules/
87-
action.yml
88-
package.json
89-
package-lock.json
90-
retention-days: 1
91-
92-
# Deploy job that uses the built artifacts and has access to secrets
93-
deploy:
94-
needs: build
95-
environment: Automation test # this environment requires approval before running the action
96-
runs-on: ${{ matrix.os }}
97-
permissions:
98-
checks: write
99-
id-token: write # This is needed for Azure login with OIDC
100-
continue-on-error: true
101-
strategy:
102-
matrix:
103-
os: [windows-latest, ubuntu-latest]
104-
105-
env:
106-
TEST_DB: 'SqlActionTest-${{ matrix.os }}'
107-
108-
steps:
109-
- name: Checkout base repository (for test data only)
110-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
49+
sa-password: ${{ env.SA_PASSWORD }}
11150

112-
- name: Download build artifact
113-
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
51+
- name: Set up SQL Server (Windows)
52+
if: runner.os == 'Windows'
53+
uses: ./.github/actions/setup-sql-windows
11454
with:
115-
name: action-build-${{ matrix.os }}
116-
path: .
55+
sa-password: ${{ env.SA_PASSWORD }}
56+
57+
- name: Build GitHub Action
58+
run: npm ci --ignore-scripts && npm run build
11759

11860
- name: Setup .NET
11961
uses: actions/setup-dotnet@v4
12062
with:
121-
dotnet-version: '8.x'
122-
- name: Install SqlPackage (Linux only)
123-
if: runner.os == 'Linux'
124-
run: dotnet tool install -g microsoft.sqlpackage
63+
dotnet-version: '10.x'
12564

126-
- name: Azure Login
127-
uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0
128-
with:
129-
client-id: ${{ secrets.AZURE_CLIENT_ID }}
130-
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
131-
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
65+
- name: Install SqlPackage
66+
run: dotnet tool install -g microsoft.sqlpackage
13267

133-
# Deploy a DACPAC with only a table to server
68+
# Deploy a DACPAC with only a table to server (sqlpackage creates the DB if needed)
13469
- name: Test DACPAC Action
13570
uses: ./
13671
with:
137-
connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=${{ env.TEST_DB }};Authentication=Active Directory Default;'
72+
connection-string: '${{ env.BASE_CS }}Initial Catalog=${{ env.TEST_DB }};'
13873
path: ./__testdata__/sql-action.dacpac
13974
action: 'publish'
75+
skip-firewall-check: true
14076

14177
# Build and publish sqlproj that should create a new view
14278
- name: Test Build and Publish
14379
uses: ./
14480
with:
145-
connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=${{ env.TEST_DB }};Authentication=Active Directory Default;'
81+
connection-string: '${{ env.BASE_CS }}Initial Catalog=${{ env.TEST_DB }};'
14682
path: ./__testdata__/TestProject/sql-action.sqlproj
14783
action: 'publish'
84+
skip-firewall-check: true
14885

14986
# Execute testsql.sql via script action on server
15087
- name: Test SQL Action
15188
uses: ./
15289
with:
153-
connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=${{ env.TEST_DB }};Authentication=Active Directory Default;'
90+
connection-string: '${{ env.BASE_CS }}Initial Catalog=${{ env.TEST_DB }};'
15491
path: ./__testdata__/testsql.sql
92+
skip-firewall-check: true
15593

15694
- name: Cleanup Test Database
15795
if: always()
15896
uses: ./
159-
with:
160-
connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=master;Authentication=Active Directory Default;'
97+
with:
98+
connection-string: '${{ env.BASE_CS }}Initial Catalog=master;'
16199
path: ./__testdata__/cleanup.sql
162100
arguments: '-v DbName="${{ env.TEST_DB }}"'
101+
skip-firewall-check: true
102+
103+
- name: Stop SQL Server container (Linux)
104+
if: always() && runner.os == 'Linux'
105+
run: docker rm -f sqlserver || true

0 commit comments

Comments
 (0)