| applyTo |
|---|
eng/pipelines/**/*.yml |
This document describes the design of the unified Azure DevOps YAML pipeline that builds, signs, packages, and optionally releases six NuGet packages with interdependencies. The pipeline uses stages and jobs to maximize parallelism while respecting dependency order. It comprises five stages: three build stages, a validation stage, and an on-demand release stage.
Two pipeline variants exist from the same stage/job structure:
| Pipeline | Template | Trigger | Purpose |
|---|---|---|---|
dotnet-sqlclient-official-pipeline.yml |
OneBranch.Official.CrossPlat.yml |
CI + scheduled | Production-signed builds |
dotnet-sqlclient-non-official-pipeline.yml |
OneBranch.NonOfficial.CrossPlat.yml |
Manual only | Validation / test builds (release in dry-run mode) |
Both pipelines use the OneBranch (1ES) governed template infrastructure and share identical stage definitions, job templates, and variable chains.
| # | Package | Dependencies |
|---|---|---|
| 1 | Microsoft.SqlServer.Server |
— |
| 2 | Microsoft.Data.SqlClient.Internal.Logging |
— |
| 3 | Microsoft.Data.SqlClient.Extensions.Abstractions |
Internal.Logging |
| 4 | Microsoft.Data.SqlClient |
Internal.Logging, Extensions.Abstractions |
| 5 | Microsoft.Data.SqlClient.Extensions.Azure |
Extensions.Abstractions, Internal.Logging |
| 6 | Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider |
SqlClient, Internal.Logging |
sequenceDiagram
participant T as Trigger / User
participant P as Pipeline Orchestrator
participant B1a as Job: Build Internal.Logging
participant B1c as Job: Build SqlServer.Server
participant B1b as Job: Build Extensions.Abstractions
participant B2a as Job: Build SqlClient
participant B2b as Job: Build Extensions.Azure
participant V as Job: Validate MDS Package
participant B3 as Job: Build AKV Provider
participant R as Stage: Release
Note over T,R: ══════ BUILD & SIGN PHASE ══════
T->>P: Pipeline triggered (CI / Scheduled / Manual)
Note over P: Stage 1 — build_independent (parallel, no deps)
par Stage 1 jobs (parallel)
P->>B1a: Build DLLs → ESRP sign DLLs → Pack → ESRP sign NuGet (Logging)
B1a-->>P: ✅ Signed .nupkg
and
P->>B1c: Build + ESRP sign + pack SqlServer.Server
B1c-->>P: ✅ Signed .nupkg
end
Note over P: Stage 2 — build_abstractions (dependsOn: build_independent)
P->>B1b: Build DLLs → ESRP sign DLLs → Pack → ESRP sign NuGet (Abstractions)
Note right of B1b: Downloads: Internal.Logging artifact
B1b-->>P: ✅ Signed .nupkg
Note over P: Stage 3 — build_dependent (dependsOn: build_abstractions)
par Stage 3 jobs (parallel)
P->>B2a: Build + ESRP sign + pack SqlClient
Note right of B2a: Downloads: Internal.Logging,<br/>Extensions.Abstractions artifacts
B2a-->>P: ✅ Signed .nupkg + .snupkg
and
P->>B2b: Build DLLs → ESRP sign DLLs → Pack → ESRP sign NuGet (Azure)
Note right of B2b: Downloads: Extensions.Abstractions,<br/>Internal.Logging artifacts
B2b-->>P: ✅ Signed .nupkg
end
Note over P: Validation + Stage 4 (both dependsOn: build_dependent, run in parallel)
par Validation and Stage 4 (parallel)
P->>V: Validate signed MDS package
V-->>P: ✅ Package validation passed
and
P->>B3: Build + ESRP sign + pack AKV Provider
Note right of B3: Downloads: SqlClient,<br/>Internal.Logging artifacts
B3-->>P: ✅ Signed .nupkg
end
Note over T,R: ══════ RELEASE PHASE (on-demand) ══════
alt At least one release parameter is true
P->>R: Stage: release (dependsOn: conditional on build stages)
Note right of R: ADO Environment Approval<br/>(NuGet-Production environment)
R-->>P: ✅ Approved
Note right of R: Publish selected packages<br/>via NuGetCommand@2
R-->>P: ✅ Published to NuGet
else No release parameters set
Note over P: Release stage skipped
end
Note over T,R: Pipeline complete 🎉
The build phase runs automatically on every CI trigger, scheduled run, or manual queue. It is divided into four build stages plus a validation stage, based on the dependency graph.
| Job Template | Package | Build Target | Condition |
|---|---|---|---|
build-signed-csproj-package-job.yml |
Microsoft.Data.SqlClient.Internal.Logging |
BuildLogging / PackLogging |
buildAKVProvider OR buildSqlClient |
build-signed-csproj-package-job.yml |
Microsoft.SqlServer.Server |
PackSqlServer |
buildSqlServerServer |
dependsOn: none- Parallelism: Jobs run in parallel (depending on which are enabled)
- Conditional builds: Each job is wrapped with compile-time
${{ if }}conditionals based on build parameters - csproj-based jobs (
build-signed-csproj-package-job.yml) perform: Build DLLs → ESRP DLL signing → NuGet pack (NoBuild=true) → ESRP NuGet signing → publish artifact
| Job Template | Package | Build Target | Artifact Dependencies |
|---|---|---|---|
build-signed-csproj-package-job.yml |
Microsoft.Data.SqlClient.Extensions.Abstractions |
BuildAbstractions / PackAbstractions |
Internal.Logging |
- Stage condition:
buildSqlClient = true(entire stage is excluded when false) dependsOn:build_independent- Downloads
Microsoft.Data.SqlClient.Internal.Logging.nupkg(from Stage 1) pipeline artifact
| Job Template | Package | Build Target | Artifact Dependencies |
|---|---|---|---|
build-signed-package-job.yml |
Microsoft.Data.SqlClient |
(nuspec-based) | Internal.Logging, Extensions.Abstractions |
build-signed-csproj-package-job.yml |
Microsoft.Data.SqlClient.Extensions.Azure |
BuildAzure / PackAzure |
Extensions.Abstractions, Internal.Logging |
- Stage condition:
buildSqlClient = true(entire stage is excluded when false) dependsOn:build_abstractions- Parallelism: Both jobs run in parallel
- The MDS (SqlClient) job also publishes symbol packages (
.snupkg) whenpublishSymbolsis true - All jobs configure APIScan with job-level
ob_sdl_apiscan_*variables targeting package-specific folders
| Job Template | Package | Artifact Dependencies |
|---|---|---|
build-akv-official-job.yml |
Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider |
SqlClient, Internal.Logging |
- Stage condition:
buildAKVProvider AND buildSqlClient(both must be true) dependsOn:build_dependent- Downloads
Microsoft.Data.SqlClient.nupkg(from Stage 3) andMicrosoft.Data.SqlClient.Internal.Logging.nupkg(from Stage 1) pipeline artifacts - Uses separate ESRP signing credentials (
Signing-prefixed variables fromesrp-variables-v2group)
Validates the signed MDS (SqlClient) package after Stage 3 completes.
- Stage condition:
buildSqlClient = true dependsOn:build_dependent- Runs in parallel with Stage 4 (
build_addons) - Uses
validate-signed-package-job.ymltemplate - Downloads the
drop_build_dependent_build_signed_packageartifact and validates againstCurrentNetFxVersion(default:net462)
The release stage is gated and only executes on demand when at least one release parameter is set to true at queue time.
dependsOn: Conditional based on which build stages are enabled:build_independent(when releasing SqlServer.Server or Logging)build_abstractions(when releasing Abstractions)build_dependent,mds_package_validation(whenbuildSqlClient = true)build_addons(whenbuildAKVProvider AND buildSqlClient)
- Gate: ADO Environment approvals (official pipeline only):
- Official:
NuGet-Productionenvironment with configured approvals - Non-Official:
NuGet-DryRunenvironment (no approvals, validation only)
- Official:
- Package selection: Controlled by 6 runtime boolean parameters (see Section 5.2)
- Stage condition: The entire stage is skipped unless at least one release parameter is
true:- ${{ if or(parameters.releaseSqlServerServer, parameters.releaseLogging, ...) }}: - stage: release
- Publish jobs: Each package has a conditional publish job that is included at compile time only when its parameter is
true:- ${{ if eq(parameters.releaseXxx, true) }}: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self
- Environment variables: Stage sets
ob_release_usedeploymentjob: truefor OneBranch integration:- Official:
ob_release_environment: 'NuGet-Production' - Non-Official:
ob_release_environment: 'NuGet-DryRun'
- Official:
| Package | Artifact Name | Publish Job |
|---|---|---|
Microsoft.SqlServer.Server |
drop_build_independent_build_package_SqlServer |
publish_SqlServer_Server |
Microsoft.Data.SqlClient.Internal.Logging |
drop_build_independent_build_package_Logging |
publish_Logging |
Microsoft.Data.SqlClient.Extensions.Abstractions |
drop_build_abstractions_build_package_Abstractions |
publish_Abstractions |
Microsoft.Data.SqlClient |
drop_build_dependent_build_package_SqlClient |
publish_SqlClient |
Microsoft.Data.SqlClient.Extensions.Azure |
drop_build_dependent_build_package_Azure |
publish_Extensions_Azure |
Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider |
drop_build_addons_buildSignedAkvPackage |
publish_AKVProvider |
Each publish job uses the reusable publish-nuget-package-job.yml template, which downloads the artifact and pushes .nupkg/.snupkg files via NuGetCommand@2 with an external feed service connection.
Two ADO environments control release behavior:
| Environment | Pipeline | Behavior |
|---|---|---|
NuGet-DryRun |
Non-Official | Validation only — packages are never pushed |
NuGet-Production |
Official | Real releases with approval gate |
Non-official pipeline: Always runs in dry-run mode. There is no releaseDryRun parameter — dryRun: true is hardcoded in every publish job. This prevents accidental publication from validation builds.
Official pipeline: Exposes a releaseDryRun parameter (default: true for safety). When enabled, the template downloads artifacts and lists the .nupkg/.snupkg files that would be published but skips the actual NuGetCommand@2 push. Set releaseDryRun: false to perform real pushes after final validation.
The pipeline exposes the following parameters at queue time:
parameters:
- name: debug
displayName: 'Enable debug output'
type: boolean
default: false
- name: publishSymbols
displayName: 'Publish symbols'
type: boolean
default: false
- name: CurrentNetFxVersion
displayName: 'Lowest supported .NET Framework version (MDS validation)'
type: string
default: 'net462'
- name: isPreview
displayName: 'Is this a preview build?'
type: boolean
default: false
- name: testJobTimeout
displayName: 'Test job timeout (in minutes)'
type: number
default: 60
# Build parameters — control which packages to build
- name: buildSqlServerServer
displayName: 'Build Microsoft.SqlServer.Server'
type: boolean
default: true
- name: buildSqlClient
displayName: 'Build Microsoft.Data.SqlClient and Extensions'
type: boolean
default: true
- name: buildAKVProvider
displayName: 'Build Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider'
type: boolean
default: trueThe isPreview parameter controls version resolution — when true, each package uses its preview version (e.g., loggingPackagePreviewVersion) instead of the GA version (e.g., loggingPackageVersion). All versions are defined in the centralized libraries/common-variables.yml.
The build parameters enable selective package building:
buildSqlServerServer— controls SqlServer.Server build jobbuildSqlClient— controls MDS, Extensions.Azure, Abstractions, Logging (when AKV is disabled), and validation stagesbuildAKVProvider— controls AKV Provider build (also requiresbuildSqlClient=true) and Logging (when SqlClient is disabled)
When set to false, the respective jobs/stages are excluded at compile-time using ${{ if }} conditionals. This allows faster pipeline runs when only certain packages need to be built.
Six boolean parameters control selective package release. All default to false so the release stage is skipped on normal CI/scheduled builds:
parameters:
- name: releaseSqlServerServer
displayName: 'Release Microsoft.SqlServer.Server'
type: boolean
default: false
- name: releaseLogging
displayName: 'Release Microsoft.Data.SqlClient.Internal.Logging'
type: boolean
default: false
- name: releaseAbstractions
displayName: 'Release Microsoft.Data.SqlClient.Extensions.Abstractions'
type: boolean
default: false
- name: releaseSqlClient
displayName: 'Release Microsoft.Data.SqlClient'
type: boolean
default: false
- name: releaseAzure
displayName: 'Release Microsoft.Data.SqlClient.Extensions.Azure'
type: boolean
default: false
- name: releaseAKVProvider
displayName: 'Release Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider'
type: boolean
default: falseThe official pipeline includes a releaseDryRun parameter that defaults to true for safety:
- name: releaseDryRun
displayName: 'Release Dry Run (do not push to NuGet)'
type: boolean
default: true # safety default — must explicitly disable for real releasesWhen releaseDryRun: true, publish jobs download artifacts and list packages but skip actual NuGet push. Set to false for production releases.
Note: The non-official pipeline does not expose this parameter — dry-run mode is hardcoded and cannot be disabled.
Variables are defined in a layered template chain. All variable groups live inside the templates — none are declared inline at the pipeline level:
dotnet-sqlclient-official-pipeline.yml
└─ libraries/variables.yml
└─ libraries/build-variables.yml
├─ group: 'Release Variables'
├─ group: 'Symbols publishing' ← SymbolsPublishServer, SymbolsPublishTokenUri, etc.
└─ libraries/common-variables.yml
├─ group: 'ESRP Federated Creds (AME)' ← ESRP signing credentials
├─ SymbolServer / SymbolTokenUri aliases ← mapped from Symbols publishing group
└─ all package versions, paths, build variables
All package versions are centralized in libraries/common-variables.yml:
| Package | GA Version Var | Preview Version Var | Assembly Version Var |
|---|---|---|---|
| Logging | loggingPackageVersion |
loggingPackagePreviewVersion |
loggingAssemblyFileVersion |
| Abstractions | abstractionsPackageVersion |
abstractionsPackagePreviewVersion |
abstractionsAssemblyFileVersion |
| SqlServer.Server | sqlServerPackageVersion |
sqlServerPackagePreviewVersion |
sqlServerAssemblyFileVersion |
| SqlClient (MDS) | mdsPackageVersion |
mdsPackagePreviewVersion |
mdsAssemblyFileVersion |
| Extensions.Azure | azurePackageVersion |
azurePackagePreviewVersion |
azureAssemblyFileVersion |
| AKV Provider | akvPackageVersion |
akvPackagePreviewVersion |
akvAssemblyFileVersion |
The pipeline resolves effective*Version variables at compile time based on the isPreview parameter.
| Variable | Defined In | Purpose |
|---|---|---|
NuGetServiceConnection |
libraries/common-variables.yml |
External NuGet service connection name for NuGetCommand@2 push |
SymbolServer |
libraries/common-variables.yml (alias) |
Alias for $(SymbolsPublishServer) — used by MDS publish-symbols-step.yml |
SymbolTokenUri |
libraries/common-variables.yml (alias) |
Alias for $(SymbolsPublishTokenUri) — used by MDS publish-symbols-step.yml |
| Group | Included In | Purpose |
|---|---|---|
Release Variables |
build-variables.yml |
Release-specific configuration |
Symbols publishing |
build-variables.yml |
Symbol publishing credentials (SymbolsAzureSubscription, SymbolsPublishServer, SymbolsPublishTokenUri, SymbolsUploadAccount, SymbolsPublishProjectName) |
ESRP Federated Creds (AME) |
common-variables.yml |
Federated identity for ESRP signing (ESRPConnectedServiceName, ESRPClientId, AppRegistrationClientId, AppRegistrationTenantId, AuthAKVName, AuthSignCertName) |
All packages are signed using ESRP (Enterprise Security Release Pipeline) with federated identity authentication.
- Build DLLs only —
build.projtarget (e.g.,BuildLogging) compiles assemblies without creating NuGet packages - ESRP DLL signing — Assemblies are signed with Authenticode certificates via ESRP
- NuGet pack —
build.projpack target (e.g.,PackLogging) creates.nupkgfrom signed DLLs usingNoBuild=true - ESRP NuGet signing — The
.nupkgfiles are signed with NuGet certificates via ESRP
This workflow ensures the NuGet package contains signed DLLs rather than signing the NuGet package around unsigned assemblies.
- Build + pack — MSBuild creates both assemblies and NuGet packages
- ESRP DLL signing — Assemblies are signed with Authenticode certificates via ESRP
- ESRP NuGet signing — The
.nupkgfiles are signed with NuGet certificates via ESRP
- Extension packages (Logging, Abstractions, Azure, SqlServer.Server, SqlClient) use the primary ESRP credentials from the
ESRP Federated Creds (AME)variable group (loaded viacommon-variables.yml) - AKV Provider uses separate
Signing-prefixed credential parameters that are passed explicitly to thebuild-akv-official-job.ymltemplate - All credentials are sourced from Azure Key Vault and federated identity — no secrets stored in pipeline YAML
Both pipelines use OneBranch governed templates for 1ES compliance. The SDL configuration differs between Official and Non-Official:
| SDL Tool | Official | Non-Official | Purpose |
|---|---|---|---|
| TSA | ✅ enabled: true |
❌ enabled: false |
Uploads SDL results to TSA for downstream analysis |
| ApiScan | ✅ enabled: true, break: true |
✅ enabled: true, break: true |
Scans APIs for compliance issues |
| CodeQL | ✅ (non-preview) | ✅ (non-preview) | Static analysis for security vulnerabilities |
| SBOM | ✅ (non-preview) | ✅ (non-preview) | Software Bill of Materials generation |
| Policheck | ✅ break: true |
✅ break: true |
Scans for policy-violating content |
| BinSkim | ✅ (async, non-preview) | ✅ (async, non-preview) | Binary security analysis |
| CredScan | ✅ (async, non-preview) | ✅ (async, non-preview) | Credential leak detection |
| Roslyn | ✅ (async, non-preview) | ✅ (async, non-preview) | Roslyn-based security analyzers |
| Armory | ✅ break: true |
✅ break: true |
Additional security scanning |
APIScan is configured at both pipeline level and job level:
Pipeline-level (globalSdl:apiscan:): Sets default configuration inherited by all jobs. This is configured for MDS (Microsoft.Data.SqlClient) as the primary product.
Job-level (ob_sdl_apiscan_* variables): Each build job overrides the pipeline defaults with package-specific settings:
| Variable | Purpose |
|---|---|
ob_sdl_apiscan_enabled |
Enable/disable APIScan for this job (true) |
ob_sdl_apiscan_softwareFolder |
Path to signed DLLs for scanning |
ob_sdl_apiscan_symbolsFolder |
Path to PDBs for scanning |
ob_sdl_apiscan_softwarename |
Package name (e.g., Microsoft.Data.SqlClient.Internal.Logging) |
ob_sdl_apiscan_versionNumber |
Assembly file version |
Each job copies its signed DLLs and PDBs to a package-specific folder under $(Build.SourcesDirectory)/apiScan/<PackageName>/ after ESRP DLL signing, ensuring APIScan analyzes the correct signed binaries for each package.
PRC Compliance: The Official pipeline hardcodes
OneBranch.Official.CrossPlat.yml(not parameterized) to satisfy Production Readiness Check static verification requirements.
- Each build job publishes its output as a pipeline artifact managed by OneBranch's
ob_outputDirectoryconvention. - Artifact names follow the OneBranch auto-generated pattern:
drop_<stageName>_<jobName>(e.g.,drop_build_dependent_build_package_SqlClient). - Downstream stages use
DownloadPipelineArtifact@2to pull required packages into a local directory. - A local NuGet source is configured at build time pointing to the downloaded artifacts directory so
dotnet restoreresolves internal dependencies.
trigger:
branches:
include:
- internal/main
paths:
include:
- .azuredevops
- .config
- doc
- eng/pipelines
- src
- tools
- azurepipelines-coverage.yml
- build.proj
- NuGet.config
schedules:
- cron: '30 4 * * Mon' # Weekly Sunday 9:30 PM (UTC-7)
branches: { include: [internal/main] }
always: true
- cron: '30 3 * * Mon-Fri' # Weekday 8:30 PM (UTC-7)
branches: { include: [internal/main] }- CI trigger: Runs on pushes to
internal/mainwhen relevant paths change - Scheduled: Weekly full build (Sundays) + weekday builds (Mon–Fri)
- No PR trigger: Official pipeline should not run on PRs (separate PR pipelines exist)
trigger: none
pr: none- Manual only: Queued on-demand for validation/test builds
| Concern | Implementation |
|---|---|
| Pipeline template | OneBranch governed templates (OneBranch.Pipelines/GovernedTemplates) |
| Build agents | OneBranch-managed Windows containers (WindowsHostVersion: 1ESWindows2022) |
| .NET SDK | Pinned via global.json (with useGlobalJson: true in install steps) |
| Code signing | ESRP v2 with federated identity (Azure Key Vault backed) |
| Symbol publishing | Optional, controlled by publishSymbols parameter; uses Symbols publishing variable group (aliases SymbolServer/SymbolTokenUri defined in common-variables.yml) |
- Single pipeline, multiple stages — avoids managing 6 separate pipelines while keeping clear separation of concerns.
- Official + Non-Official variants — hardcoded OneBranch templates (no parameterized
oneBranchType) for PRC compliance; Non-Official variant allows manual validation builds. - Parallel jobs within stages — minimizes total wall-clock time; only waits where dependencies demand it.
- Pipeline artifacts over Universal Packages — faster, ephemeral, scoped to the run; appropriate for build-time dependency resolution.
- ESRP-based code signing — all DLLs and NuGet packages are signed in-pipeline using ESRP with federated identity; no secrets in YAML.
- Centralized version management — all 6 package versions (GA + preview) defined once in
libraries/common-variables.yml;isPreviewtoggle selects the active set. - Dependency-aware stage ordering — ensures packages are always built after their dependencies, guaranteeing consistent, reproducible builds.
- Validation in parallel with Stage 3 — MDS package validation runs alongside AKV Provider build (both depend on Stage 2), reducing total pipeline duration.
- Selective on-demand release — 6 boolean parameters control which packages are published; the release stage is entirely skipped when none are selected, keeping normal CI builds unaffected.
- ADO Environment approval gate — two environments:
NuGet-Production(official, with configured approvals) andNuGet-DryRun(non-official, validation only). Both useob_release_environmentfor OneBranch integration. - Compile-time conditional publish jobs —
${{ if eq(parameters.releaseXxx, true) }}template expansion ensures unselected publish jobs are excluded entirely from the pipeline run (not just skipped at runtime). - Mandatory dry-run for non-official — the non-official variant hardcodes
dryRun: true(no parameter), preventing accidental publication. The official variant defaultsreleaseDryRun: truefor safety but allows override for actual releases. - Selective build parameters —
buildSqlClient,buildSqlServerServer, andbuildAKVProviderallow building subsets of packages, with dependency-aware conditionals ensuring Logging builds when either SqlClient or AKV is needed.