Conversation
Add a GitHub Actions workflow that validates dependency version bumps in Directory.Packages.props do not break the DurableTask SDK NuGet packages when consumed by Azure Functions. The pipeline: - Packs all SDK packages from source into a local NuGet feed - Builds a .NET isolated Function App using PackageReferences (not ProjectReferences) to the local packages - Starts Azurite and runs the Function App in Docker - Triggers a HelloCities orchestration and validates output - Verifies loaded SDK assembly versions match the locally built version This mirrors the SDK-PR-Validation pipeline pattern used in AAPT-DTMB.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new GitHub Actions workflow and a dedicated Azure Functions (.NET isolated) smoke-test app to validate that dependency-version bumps don’t break the locally packed DurableTask SDK NuGet packages when consumed via PackageReference.
Changes:
- Added
dep-version-validation.ymlworkflow to pack SDK NuGets, build/publish a Functions app against a local feed, run Azurite + the app in Docker, and validate runtime behavior and loaded assembly versions. - Added
test/SmokeTests/DepValidationFunction App project (host/program/functions) configured to restore DurableTask packages from a local feed via package source mapping. - Added Dockerfile + local NuGet feed ignore to support containerized execution in CI.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/SmokeTests/DepValidation/local.settings.json | Adds local dev settings for the smoke-test Function App. |
| test/SmokeTests/DepValidation/host.json | Adds Functions host configuration for the smoke-test app. |
| test/SmokeTests/DepValidation/Program.cs | Minimal .NET isolated worker host startup. |
| test/SmokeTests/DepValidation/NuGet.config | Forces DurableTask package resolution from the local feed using package source mapping. |
| test/SmokeTests/DepValidation/HelloCitiesOrchestration.cs | Adds orchestration + an HTTP endpoint to report loaded SDK assembly versions. |
| test/SmokeTests/DepValidation/Dockerfile | Packages the published Function App into an Azure Functions base image. |
| test/SmokeTests/DepValidation/DepValidationApp.csproj | Defines dependencies (Functions worker + DurableTask SDK from local feed). |
| test/SmokeTests/DepValidation/.gitignore | Ignores locally generated local-packages/ feed. |
| .github/workflows/dep-version-validation.yml | Implements the end-to-end dependency version validation pipeline in CI. |
| - name: Parse SDK version | ||
| id: version | ||
| run: | | ||
| set -e | ||
| PROPS_FILE="eng/targets/Release.props" | ||
| VERSION_PREFIX=$(grep -oP '<VersionPrefix>\K[^<]+' "$PROPS_FILE") | ||
| VERSION_SUFFIX=$(grep -oP '<VersionSuffix>\K[^<]+' "$PROPS_FILE" || true) | ||
|
|
||
| # Bump patch version to distinguish local build from published | ||
| IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_PREFIX" | ||
| LOCAL_PATCH=$((PATCH + 1)) | ||
| LOCAL_VERSION="${MAJOR}.${MINOR}.${LOCAL_PATCH}" | ||
| if [ -n "$VERSION_SUFFIX" ]; then | ||
| LOCAL_VERSION="${LOCAL_VERSION}-${VERSION_SUFFIX}" | ||
| fi | ||
|
|
||
| # Update the version in source so assembly versions are consistent | ||
| sed -i "s|<VersionPrefix>${VERSION_PREFIX}</VersionPrefix>|<VersionPrefix>${MAJOR}.${MINOR}.${LOCAL_PATCH}</VersionPrefix>|" "$PROPS_FILE" | ||
|
|
||
| echo "Published version: ${VERSION_PREFIX}" | ||
| echo "Local version: ${LOCAL_VERSION}" | ||
| echo "sdk_version=${LOCAL_VERSION}" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
The version parsing/mutation is brittle: grep -P and the exact-string sed replacement will break if Release.props formatting changes (whitespace, attributes, multi-line XML), and mutating tracked files in-place makes failures harder to diagnose. Prefer deriving the version via MSBuild and passing it as properties (e.g., set VersionPrefix/VersionSuffix/Version via dotnet build/pack -p:...) rather than editing eng/targets/Release.props.
| - name: Start Azurite | ||
| run: | | ||
| npm install -g azurite | ||
| docker network create smoketest-network 2>/dev/null || true | ||
| docker run -d \ | ||
| --name azurite-smoketest \ | ||
| --network smoketest-network \ | ||
| -p 10000:10000 -p 10001:10001 -p 10002:10002 \ | ||
| mcr.microsoft.com/azure-storage/azurite |
There was a problem hiding this comment.
npm install -g azurite is unused (Azurite is started via the Docker image), adds time and introduces an extra external supply-chain dependency. Remove the npm install step and rely solely on the container.
| # ---- Verify SDK packages resolved from local-packages ---- | ||
| - name: Verify SDK packages from local source | ||
| run: | | ||
| set -e | ||
| echo "Verifying SDK packages were restored from local-packages..." | ||
| ASSETS_FILE="test/SmokeTests/DepValidation/obj/project.assets.json" | ||
| for pkg in "Microsoft.DurableTask.Abstractions" "Microsoft.DurableTask.Client.Grpc" "Microsoft.DurableTask.Worker.Grpc"; do | ||
| if grep -qi "$pkg" "$ASSETS_FILE"; then | ||
| echo " OK: $pkg found in project.assets.json" | ||
| else | ||
| echo " FAIL: $pkg NOT found" | ||
| exit 1 | ||
| fi | ||
| done | ||
| echo "PASS: SDK packages verified in build output." |
There was a problem hiding this comment.
This check does not actually verify the packages were restored from the local feed—it only verifies the package IDs appear in project.assets.json, which would also be true if they were restored from nuget.org. To make this a real guardrail, run restore explicitly with the intended NuGet.config (e.g., dotnet restore ... --configfile test/SmokeTests/DepValidation/NuGet.config) and/or rely on packageSourceMapping by making the restore fail if local-packages is missing (e.g., temporarily disabling nuget.org for the Microsoft.DurableTask.* pattern during this verification step).
| --name azurite-smoketest \ | ||
| --network smoketest-network \ | ||
| -p 10000:10000 -p 10001:10001 -p 10002:10002 \ | ||
| mcr.microsoft.com/azure-storage/azurite |
There was a problem hiding this comment.
The Azurite image is unpinned (defaults to latest), which can introduce CI flakiness when the image updates. Pin to a specific tag (or ideally an image digest) to make the workflow deterministic.
| mcr.microsoft.com/azure-storage/azurite | |
| mcr.microsoft.com/azure-storage/azurite:3.33.0 |
| string[] sdkAssemblyPrefixes = new[] | ||
| { | ||
| "Microsoft.DurableTask.Abstractions", | ||
| "Microsoft.DurableTask.Client", | ||
| "Microsoft.DurableTask.Client.Grpc", | ||
| "Microsoft.DurableTask.Worker", | ||
| "Microsoft.DurableTask.Worker.Grpc", | ||
| "Microsoft.DurableTask.Grpc", | ||
| }; | ||
|
|
||
| SortedDictionary<string, string> loadedVersions = new(); | ||
| foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) | ||
| { | ||
| AssemblyName name = asm.GetName(); | ||
| foreach (string prefix in sdkAssemblyPrefixes) | ||
| { | ||
| if (string.Equals(name.Name, prefix, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| string? infoVersion = asm | ||
| .GetCustomAttribute<AssemblyInformationalVersionAttribute>() | ||
| ?.InformationalVersion; | ||
|
|
||
| // Strip source-link commit hash suffix (e.g. "1.23.3+abc123") | ||
| if (infoVersion != null && infoVersion.Contains('+')) | ||
| { | ||
| infoVersion = infoVersion[..infoVersion.IndexOf('+')]; | ||
| } | ||
|
|
||
| loadedVersions[name.Name!] = infoVersion ?? name.Version?.ToString() ?? "unknown"; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The version endpoint only reports assemblies that are already loaded into the AppDomain. If one of the expected DurableTask assemblies hasn’t been loaded yet, it will be silently omitted and the workflow’s version check won’t detect it. Consider explicitly loading/forcing references to the expected assemblies (e.g., via typeof(...) on a type from each package) and/or returning a result that includes all expected assembly names (failing/marking missing ones) so the pipeline can assert completeness, not just version equality for whatever happened to be loaded.
| <!-- Azure Functions host packages (from nuget.org) --> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.51.0" /> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" /> | ||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" OutputItemType="Analyzer" /> |
There was a problem hiding this comment.
For the Functions Worker SDK analyzer reference, it’s typical to set PrivateAssets=\"all\" (and, if needed, IncludeAssets) to ensure it never flows to consumers via transitive dependencies. This project isn’t packable, but adding PrivateAssets=\"all\" makes the intent explicit and prevents accidental propagation if project settings change later.
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" OutputItemType="Analyzer" /> | |
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" OutputItemType="Analyzer" PrivateAssets="all" /> |
| foreach (string prefix in sdkAssemblyPrefixes) | ||
| { | ||
| if (string.Equals(name.Name, prefix, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| string? infoVersion = asm | ||
| .GetCustomAttribute<AssemblyInformationalVersionAttribute>() | ||
| ?.InformationalVersion; | ||
|
|
||
| // Strip source-link commit hash suffix (e.g. "1.23.3+abc123") | ||
| if (infoVersion != null && infoVersion.Contains('+')) | ||
| { | ||
| infoVersion = infoVersion[..infoVersion.IndexOf('+')]; | ||
| } | ||
|
|
||
| loadedVersions[name.Name!] = infoVersion ?? name.Version?.ToString() ?? "unknown"; | ||
| } | ||
| } |
|
Closing — durabletask-dotnet packages are transitive dependencies consumed through azure-functions-durable-extension and AAPT-DTMB. The dep validation is more valuable in those downstream repos. This repo already has azure-functions-smoke-tests.yml for code-level validation. |
Summary
Adds a GitHub Actions workflow (dep-version-validation.yml) that validates dependency version bumps in Directory.Packages.props do not break the DurableTask SDK NuGet packages when consumed by Azure Functions.
What this pipeline does
Trigger paths
Motivation
The existing \�zure-functions-smoke-tests.yml\ uses \ProjectReference\ for the local SDK packages. This is great for validating code changes but doesn't catch NuGet-level issues (wrong version ranges, missing transitive dependencies, assembly version conflicts) that affect real users who consume these packages via NuGet.
This pipeline mirrors the SDK-PR-Validation pipeline pattern used in AAPT-DTMB.