Skip to content

Commit 644df24

Browse files
Merge branch 'main' into copilot/fix-duplicate-graphql-operations
2 parents 7d9ce09 + a5a4879 commit 644df24

142 files changed

Lines changed: 13247 additions & 416 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pipelines/cosmos-pipelines.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,7 @@ steps:
142142
inputs:
143143
codeCoverageTool: Cobertura
144144
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
145+
146+
- template: templates/publish-coverage-artifact.yml
147+
parameters:
148+
artifactName: 'coverage-cosmos'

.pipelines/dwsql-pipelines.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ jobs:
146146
codeCoverageTool: Cobertura
147147
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
148148

149+
# Publish this job's raw cobertura as a named pipeline artifact for aggregation.
150+
- template: templates/publish-coverage-artifact.yml
151+
parameters:
152+
artifactName: 'coverage-dwsql'
153+
149154

150155
- job: windows
151156
pool:
@@ -251,6 +256,11 @@ jobs:
251256
codeCoverageTool: Cobertura
252257
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
253258

259+
# Publish the Windows job's raw cobertura as a uniquely-named artifact for aggregation.
260+
- template: templates/publish-coverage-artifact.yml
261+
parameters:
262+
artifactName: 'coverage-dwsql-windows'
263+
254264
- task: CopyFiles@2
255265
condition: eq(variables['publishverify'], 'Yes')
256266
displayName: 'Copy received files to Artifact Staging'

.pipelines/mssql-pipelines.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ jobs:
151151
codeCoverageTool: Cobertura
152152
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
153153

154-
155-
# MsSql Integration Testing is split into two parallel jobs (~20 min each):
154+
# Publish this job's raw cobertura as a named pipeline artifact for aggregation.
155+
- template: templates/publish-coverage-artifact.yml
156+
parameters:
157+
artifactName: 'coverage-mssql'
156158
#
157159
# 1) windows_combined -> GraphQL, HotReload, REST, Unit, OpenApi, Auth,
158160
# Telemetry, and Caching tests.

.pipelines/mysql-pipelines.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,8 @@ jobs:
145145
inputs:
146146
codeCoverageTool: Cobertura
147147
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
148+
149+
# Publish this job's raw cobertura as a named pipeline artifact for aggregation.
150+
- template: templates/publish-coverage-artifact.yml
151+
parameters:
152+
artifactName: 'coverage-mysql'

.pipelines/pg-pipelines.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,8 @@ jobs:
140140
inputs:
141141
codeCoverageTool: Cobertura
142142
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
143+
144+
# Publish this job's raw cobertura as a named pipeline artifact for aggregation.
145+
- template: templates/publish-coverage-artifact.yml
146+
parameters:
147+
artifactName: 'coverage-pg'

.pipelines/templates/build-pipelines.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,10 @@ steps:
125125
inputs:
126126
codeCoverageTool: Cobertura
127127
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
128+
129+
# Publish this run's unit cobertura as a pipeline artifact so the
130+
# "Total Code Coverage" job (unittest-pipelines.yml) can merge it with the
131+
# database integration pipelines' coverage into one combined report.
132+
- template: publish-coverage-artifact.yml
133+
parameters:
134+
artifactName: 'coverage-unit'

.pipelines/templates/mssql-test-steps.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ steps:
142142
codeCoverageTool: Cobertura
143143
summaryFileLocation: '$(Agent.TempDirectory)/**/*cobertura.xml'
144144

145+
# Publish this job's raw cobertura as a uniquely-named artifact (suffixed per job).
146+
- template: publish-coverage-artifact.yml
147+
parameters:
148+
artifactName: 'coverage-mssql${{ parameters.artifactSuffix }}'
149+
145150
- task: CopyFiles@2
146151
condition: eq(variables['publishverify'], 'Yes')
147152
displayName: 'Copy received files to Artifact Staging'
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
# Stages this job's raw Cobertura file(s) from $(Agent.TempDirectory) and publishes
5+
# them as a named pipeline artifact, so the unit pipeline's "Total Code Coverage" job
6+
# can download and union them. PublishCodeCoverageResults@1 only exposes an HTML report
7+
# artifact (not the raw XML), so the aggregation needs this separate raw-cobertura
8+
# artifact. Pass a unique artifactName per job so names don't collide within a run.
9+
parameters:
10+
- name: artifactName
11+
type: string
12+
13+
steps:
14+
- task: CopyFiles@2
15+
displayName: 'Stage raw coverage for aggregation'
16+
inputs:
17+
sourceFolder: '$(Agent.TempDirectory)'
18+
contents: '**/*.cobertura.xml'
19+
targetFolder: '$(Build.ArtifactStagingDirectory)/coverage'
20+
- task: PublishPipelineArtifact@1
21+
displayName: 'Publish coverage artifact for aggregation'
22+
inputs:
23+
targetPath: '$(Build.ArtifactStagingDirectory)/coverage'
24+
artifact: '${{ parameters.artifactName }}'

.pipelines/unittest-pipelines.yml

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,113 @@ pr:
2121
- '*.md'
2222
- templates/**
2323

24-
pool:
25-
vmImage: 'ubuntu-latest' # examples of other options: 'macOS-10.15', 'windows-2019'
26-
2724
variables:
2825
- template: templates/variables.yml
2926

30-
steps:
31-
- template: templates/build-pipelines.yml
27+
jobs:
28+
- job: build
29+
displayName: 'Build and Unit Tests'
30+
pool:
31+
vmImage: 'ubuntu-latest'
32+
steps:
33+
- template: templates/build-pipelines.yml
34+
35+
# ---------------------------------------------------------------------------
36+
# Total Code Coverage (rolling dashboard, not a per-PR gate)
37+
# ---------------------------------------------------------------------------
38+
# Unions this run's unit coverage with the latest DB coverage and publishes one
39+
# combined report. Best-effort (continueOnError) so it never blocks unit CI.
40+
- job: total_code_coverage
41+
displayName: 'Total Code Coverage (rolling dashboard)'
42+
dependsOn: build
43+
condition: succeededOrFailed()
44+
continueOnError: true
45+
pool:
46+
vmImage: 'ubuntu-latest'
47+
steps:
48+
- checkout: self
49+
50+
# This run's unit coverage (published by the build job above).
51+
- task: DownloadPipelineArtifact@2
52+
displayName: 'Download unit coverage (current run)'
53+
continueOnError: true
54+
inputs:
55+
source: 'current'
56+
artifact: 'coverage-unit'
57+
patterns: '**/*.cobertura.xml'
58+
path: '$(Pipeline.Workspace)/unit'
59+
60+
# DB coverage: each pipeline's numeric definition ID (Azure DevOps pipeline URL
61+
# ...?definitionId=NN). Mapping: DwSql=1, MsSql=2, MySql=3, PgSql=4, Cosmos=6.
62+
# (5 is THIS unit pipeline, so it is not downloaded here.) If a pipeline is
63+
# recreated its ID changes and that DB silently drops from the union - the merge
64+
# step's -ExpectedSources warning flags any source that produced no files.
65+
# No 'artifact' name is set so ALL of a run's coverage artifacts are pulled
66+
# (some DBs publish one per job, e.g. MsSql linux + windows_combined +
67+
# windows_configuration); the pattern keeps only raw cobertura for the merger.
68+
- task: DownloadPipelineArtifact@2
69+
displayName: 'Download MsSql coverage (latest)'
70+
continueOnError: true
71+
inputs:
72+
source: 'specific'
73+
project: '$(System.TeamProject)'
74+
pipeline: '2'
75+
runVersion: 'latest'
76+
patterns: 'coverage-*/**/*.cobertura.xml'
77+
path: '$(Pipeline.Workspace)/db/mssql'
78+
- task: DownloadPipelineArtifact@2
79+
displayName: 'Download PostgreSql coverage (latest)'
80+
continueOnError: true
81+
inputs:
82+
source: 'specific'
83+
project: '$(System.TeamProject)'
84+
pipeline: '4'
85+
runVersion: 'latest'
86+
patterns: 'coverage-*/**/*.cobertura.xml'
87+
path: '$(Pipeline.Workspace)/db/pg'
88+
- task: DownloadPipelineArtifact@2
89+
displayName: 'Download MySql coverage (latest)'
90+
continueOnError: true
91+
inputs:
92+
source: 'specific'
93+
project: '$(System.TeamProject)'
94+
pipeline: '3'
95+
runVersion: 'latest'
96+
patterns: 'coverage-*/**/*.cobertura.xml'
97+
path: '$(Pipeline.Workspace)/db/mysql'
98+
- task: DownloadPipelineArtifact@2
99+
displayName: 'Download DwSql coverage (latest)'
100+
continueOnError: true
101+
inputs:
102+
source: 'specific'
103+
project: '$(System.TeamProject)'
104+
pipeline: '1'
105+
runVersion: 'latest'
106+
patterns: 'coverage-*/**/*.cobertura.xml'
107+
path: '$(Pipeline.Workspace)/db/dwsql'
108+
- task: DownloadPipelineArtifact@2
109+
displayName: 'Download CosmosDb coverage (latest)'
110+
continueOnError: true
111+
inputs:
112+
source: 'specific'
113+
project: '$(System.TeamProject)'
114+
pipeline: '6'
115+
runVersion: 'latest'
116+
patterns: 'coverage-*/**/*.cobertura.xml'
117+
path: '$(Pipeline.Workspace)/db/cosmos'
118+
119+
# Union-merge every downloaded cobertura and emit ONE combined Cobertura.xml
120+
# (test projects excluded). Dependency-free - no ReportGenerator extension.
121+
- task: PowerShell@2
122+
displayName: 'Merge cobertura union and emit combined report'
123+
continueOnError: true
124+
inputs:
125+
filePath: 'scripts/merge-coverage.ps1'
126+
arguments: '-Path "$(Pipeline.Workspace)" -OutFile "$(Build.ArtifactStagingDirectory)/merged/Cobertura.xml" -ExpectedSources unit,mssql,pg,mysql,dwsql,cosmos'
127+
pwsh: true
128+
129+
- task: PublishCodeCoverageResults@2
130+
displayName: 'Publish combined code coverage'
131+
continueOnError: true
132+
inputs:
133+
summaryFileLocation: '$(Build.ArtifactStagingDirectory)/merged/Cobertura.xml'

Dockerfile

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,78 @@ WORKDIR /src
77
COPY [".", "./"]
88
RUN dotnet build "./src/Service/Azure.DataApiBuilder.Service.csproj" -c Docker -o /out -r linux-x64
99

10-
FROM mcr.microsoft.com/dotnet/aspnet:10.0-azurelinux3.0 AS runtime
10+
# ---------------------------------------------------------------------------
11+
# Common runtime base.
12+
#
13+
# Not intended as a final build target. Both the `runtime` (root, default)
14+
# and `runtime-nonroot` (non-root, scanner-friendly) variants derive from
15+
# this stage so the shared setup stays in one place.
16+
# ---------------------------------------------------------------------------
17+
FROM mcr.microsoft.com/dotnet/aspnet:10.0-azurelinux3.0 AS runtime-base
1118

1219
COPY --from=build /out /App
1320
# Add default dab-config.json to /App in the image
1421
COPY --from=build /out/dab-config.json /App/dab-config.json
1522
WORKDIR /App
1623
ENV ASPNETCORE_URLS=http://+:5000
24+
EXPOSE 5000
1725
ENTRYPOINT ["dotnet", "Azure.DataApiBuilder.Service.dll"]
26+
27+
# ---------------------------------------------------------------------------
28+
# Non-root variant. Build explicitly with:
29+
# docker build --target runtime-nonroot -t <repo>:<version>-nonroot .
30+
#
31+
# Runs as the non-root user that ships with the
32+
# mcr.microsoft.com/dotnet/aspnet base image (UID/GID 1654, exposed via the
33+
# APP_UID env var, on the azurelinux3.0 variant). DAB does not require root,
34+
# and declaring USER explicitly sets the image's Config.User field so image
35+
# scanners (e.g. Checkmarx One) that require a non-root user in the final
36+
# stage are satisfied. We use the numeric `USER $APP_UID` form (rather than
37+
# `USER app`) per .NET container guidance: a numeric UID is friendlier to
38+
# image scanners and to Kubernetes `runAsNonRoot`/`runAsUser` checks, which
39+
# cannot resolve a username to a UID at admission time.
40+
#
41+
# Safeguards applied to minimize the chance of runtime breakage:
42+
# * Pre-create /App/logs and `chown app:app /App/logs` (non-recursive) so
43+
# the documented default file-sink path ("logs/dab-log.txt", relative to
44+
# WORKDIR /App) is writable. Ownership of the published assemblies under
45+
# /App is intentionally left unchanged - a recursive chown would
46+
# duplicate every assembly layer and roughly double the image size for
47+
# no runtime benefit, since DAB only needs write access to /App/logs.
48+
# * Default port stays at 5000, which is above 1024, so binding works
49+
# without CAP_NET_BIND_SERVICE. Users overriding ASPNETCORE_URLS to a
50+
# privileged port (<1024) must add `--cap-add=NET_BIND_SERVICE` to
51+
# `docker run` or front DAB with a reverse proxy.
52+
#
53+
# Notes for consumers of this image:
54+
# * Host bind-mounts (config, logs, certs, etc.) must be readable - and
55+
# writable, if DAB needs to write them - by UID 1654 on the host.
56+
# Either `chown -R 1654:1654 /host/path` or, in Kubernetes, set
57+
# `securityContext.fsGroup: 1654`.
58+
# * `docker exec` defaults to UID 1654. Use `docker exec --user 0`
59+
# for administrative actions inside a running container.
60+
# * Downstream Dockerfiles (FROM <this image>) that need to install
61+
# packages or write outside /App should add `USER 0` before those
62+
# instructions, then restore `USER $APP_UID` at the end.
63+
# ---------------------------------------------------------------------------
64+
FROM runtime-base AS runtime-nonroot
65+
66+
RUN mkdir -p /App/logs && chown $APP_UID:$APP_UID /App/logs
67+
USER $APP_UID
68+
69+
LABEL org.opencontainers.image.title="Data API builder (non-root)" \
70+
org.opencontainers.image.description="Data API builder running as the non-root 'app' user (UID 1654 on azurelinux3.0)." \
71+
org.opencontainers.image.source="https://github.com/Azure/data-api-builder"
72+
73+
# ---------------------------------------------------------------------------
74+
# Default (root-running) variant. Build with either:
75+
# docker build -t <repo>:<version> . # no --target needed
76+
# docker build --target runtime -t <repo>:<version> .
77+
#
78+
# This is the LAST stage in the file, so a plain `docker build` with no
79+
# --target argument produces this image. Keeping the root-running variant
80+
# as the default preserves backwards compatibility with the previously
81+
# published image - existing users see no behavior change. The non-root
82+
# variant is opt-in via `--target runtime-nonroot`.
83+
# ---------------------------------------------------------------------------
84+
FROM runtime-base AS runtime

0 commit comments

Comments
 (0)