-
Notifications
You must be signed in to change notification settings - Fork 58
338 lines (304 loc) · 13.8 KB
/
Copy pathdep-version-validation.yml
File metadata and controls
338 lines (304 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# ================================================================================================
# Dependency Version Validation Pipeline
# ================================================================================================
# Validates that changes to dependency versions (Directory.Packages.props) do not break
# the DurableTask SDK NuGet packages at build or runtime when consumed by Azure Functions.
#
# This pipeline:
# 1. Packs all DurableTask SDK NuGet packages from source into a local feed
# 2. Builds an Azure Functions v4 (.NET isolated) app using those local packages
# 3. Starts Azurite (Azure Storage emulator)
# 4. Runs the Function App and triggers a HelloCities orchestration
# 5. Validates orchestration output and loaded SDK assembly versions
# ================================================================================================
name: Dependency Version Validation
on:
push:
branches:
- main
- 'feature/**'
paths:
- 'Directory.Packages.props'
- 'Directory.Build.props'
- 'Directory.Build.targets'
- 'eng/targets/Release.props'
- 'test/SmokeTests/DepValidation/**'
pull_request:
paths:
- 'Directory.Packages.props'
- 'Directory.Build.props'
- 'Directory.Build.targets'
- 'eng/targets/Release.props'
- 'test/SmokeTests/DepValidation/**'
workflow_dispatch:
permissions:
contents: read
jobs:
dep-validation-smoke-test:
name: '.NET Isolated Smoke Test (NuGet Packages)'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
# ---- Checkout & SDK Setup ----
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET from global.json
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Setup .NET 8.0 SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# ---- Parse SDK Version ----
- 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"
# ---- Pack SDK Packages from Source ----
- name: Pack SDK packages from source
run: |
set -e
LOCAL_PACKAGES="test/SmokeTests/DepValidation/local-packages"
mkdir -p "$LOCAL_PACKAGES"
# Projects to pack (order matters for dependency resolution)
PROJECTS=(
"src/Abstractions/Abstractions.csproj"
"src/Client/Core/Client.csproj"
"src/Worker/Core/Worker.csproj"
"src/Grpc/Grpc.csproj"
"src/Client/Grpc/Client.Grpc.csproj"
"src/Worker/Grpc/Worker.Grpc.csproj"
)
# Build all projects first
for project in "${PROJECTS[@]}"; do
echo "Building $project..."
dotnet build "$project" -c Release
done
# Pack from build output
for project in "${PROJECTS[@]}"; do
echo "Packing $project..."
dotnet pack "$project" -c Release --no-build --output "$LOCAL_PACKAGES"
done
echo "Local packages:"
ls -la "$LOCAL_PACKAGES"
# ---- Verify Packed Packages ----
- name: Verify packed packages
env:
SDK_VERSION: ${{ steps.version.outputs.sdk_version }}
run: |
set -e
LOCAL_PACKAGES="test/SmokeTests/DepValidation/local-packages"
EXPECTED_PACKAGES=(
"Microsoft.DurableTask.Abstractions"
"Microsoft.DurableTask.Client"
"Microsoft.DurableTask.Client.Grpc"
"Microsoft.DurableTask.Worker"
"Microsoft.DurableTask.Worker.Grpc"
"Microsoft.DurableTask.Grpc"
)
MISSING=0
for pkg in "${EXPECTED_PACKAGES[@]}"; do
NUPKG="${pkg}.${SDK_VERSION}.nupkg"
if [ ! -f "$LOCAL_PACKAGES/$NUPKG" ]; then
echo "FAIL: Missing expected package: $NUPKG"
MISSING=$((MISSING + 1))
else
echo " OK: $NUPKG"
fi
done
if [ $MISSING -gt 0 ]; then
echo "FAIL: $MISSING expected SDK package(s) missing for version $SDK_VERSION"
echo "Available packages:"
ls -1 "$LOCAL_PACKAGES"
exit 1
fi
echo "PASS: All ${#EXPECTED_PACKAGES[@]} SDK packages verified for version $SDK_VERSION"
# ---- Build Smoke Test App ----
- name: Build smoke test app
env:
SDK_VERSION: ${{ steps.version.outputs.sdk_version }}
run: |
set -e
cd test/SmokeTests/DepValidation
dotnet build DepValidationApp.csproj -c Release -p:SmokeTestSdkVersion=$SDK_VERSION -v normal
# ---- 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."
# ---- Publish Smoke Test App ----
- name: Publish smoke test app
env:
SDK_VERSION: ${{ steps.version.outputs.sdk_version }}
run: |
set -e
cd test/SmokeTests/DepValidation
dotnet publish DepValidationApp.csproj -c Release -o ./publish -p:SmokeTestSdkVersion=$SDK_VERSION
# ---- Build Docker Image ----
- name: Build Docker image
run: |
docker build -t dep-validation-smoketest test/SmokeTests/DepValidation
# ---- Start Azurite ----
- 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
# Wait for Azurite
for i in $(seq 1 30); do
if timeout 1 bash -c "echo > /dev/tcp/127.0.0.1/10000" 2>/dev/null; then
echo "Azurite is ready."
break
fi
if [ $i -eq 30 ]; then echo "FAIL: Azurite did not start"; exit 1; fi
sleep 1
done
# ---- Start Function App ----
- name: Start Function App and run smoke test
env:
SDK_VERSION: ${{ steps.version.outputs.sdk_version }}
run: |
set -e
CONTAINER_NAME="dep-validation-smoketest-app"
TIMEOUT=120
docker run -d \
--name $CONTAINER_NAME \
--network smoketest-network \
-p 8080:80 \
-e AzureWebJobsStorage="DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite-smoketest:10000/devstoreaccount1;QueueEndpoint=http://azurite-smoketest:10001/devstoreaccount1;TableEndpoint=http://azurite-smoketest:10002/devstoreaccount1;" \
-e FUNCTIONS_WORKER_RUNTIME=dotnet-isolated \
dep-validation-smoketest
# Wait for the Function App to be ready
echo "Waiting for Function App to be ready..."
for i in $(seq 1 60); do
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/api/HelloCitiesOrchestration_HttpStart" 2>/dev/null | grep -qE "200|202|404"; then
echo "Function App is ready."
break
fi
if [ $i -eq 60 ]; then
echo "FAIL: Function App did not start within 120s"
docker logs $CONTAINER_NAME
exit 1
fi
sleep 2
done
# Give it a few more seconds to fully initialize
sleep 5
# Trigger orchestration
echo "Triggering HelloCities orchestration..."
HTTP_CODE=$(curl -s -o /tmp/start-response.json -w "%{http_code}" -X POST "http://localhost:8080/api/HelloCitiesOrchestration_HttpStart")
RESPONSE=$(cat /tmp/start-response.json)
echo "HTTP status: $HTTP_CODE"
echo "Start response: $RESPONSE"
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "202" ]; then
echo "FAIL: Orchestration start returned HTTP $HTTP_CODE"
docker logs $CONTAINER_NAME
exit 1
fi
STATUS_URL=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('statusQueryGetUri') or d.get('StatusQueryGetUri'))")
# Replace localhost references in status URL to work from host
STATUS_URL=$(echo "$STATUS_URL" | sed 's|localhost|localhost:8080|g' | sed 's|:80/|:8080/|g')
INSTANCE_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id') or d.get('Id'))")
echo "Instance ID: $INSTANCE_ID"
echo "Status URL: $STATUS_URL"
# Poll for completion
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
sleep 2
ELAPSED=$((ELAPSED + 2))
STATUS_RESPONSE=$(curl -s "$STATUS_URL")
RUNTIME_STATUS=$(echo "$STATUS_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('runtimeStatus') or d.get('RuntimeStatus') or '')")
if [ "$RUNTIME_STATUS" = "Completed" ]; then
OUTPUT=$(echo "$STATUS_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('output') or d.get('Output') or '')")
echo "Orchestration completed in ${ELAPSED}s. Output: $OUTPUT"
if echo "$OUTPUT" | grep -q "Hello, Tokyo!" && echo "$OUTPUT" | grep -q "Hello, London!" && echo "$OUTPUT" | grep -q "Hello, Seattle!"; then
echo "PASS: HelloCities orchestration returned expected output."
# Verify loaded SDK assembly versions
echo "Verifying loaded SDK assembly versions..."
VERSION_JSON=$(curl -s "http://localhost:8080/api/SdkVersionCheck")
echo "Loaded SDK assembly versions: $VERSION_JSON"
VERSION_MISMATCH=0
for asm_name in $(echo "$VERSION_JSON" | python3 -c "import sys,json; [print(k) for k in json.load(sys.stdin).keys()]"); do
asm_version=$(echo "$VERSION_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['$asm_name'])")
if [ "$asm_version" != "$SDK_VERSION" ]; then
echo "FAIL: Version mismatch for $asm_name: expected $SDK_VERSION, got $asm_version"
VERSION_MISMATCH=1
else
echo " OK: $asm_name = $asm_version"
fi
done
if [ $VERSION_MISMATCH -ne 0 ]; then
echo "FAIL: SDK assembly version mismatch detected."
docker logs $CONTAINER_NAME
exit 1
fi
echo "PASS: All loaded SDK assemblies match expected version $SDK_VERSION."
exit 0
else
echo "FAIL: Output did not contain expected greetings."
docker logs $CONTAINER_NAME
exit 1
fi
elif [ "$RUNTIME_STATUS" = "Failed" ] || [ "$RUNTIME_STATUS" = "Terminated" ]; then
echo "FAIL: Orchestration $RUNTIME_STATUS"
echo "$STATUS_RESPONSE"
docker logs $CONTAINER_NAME
exit 1
fi
done
echo "FAIL: Orchestration did not complete within ${TIMEOUT}s (last status: $RUNTIME_STATUS)"
docker logs $CONTAINER_NAME
exit 1
# ---- Collect Logs on Failure ----
- name: Collect logs on failure
if: failure()
run: |
mkdir -p smoke-test-logs
docker logs dep-validation-smoketest-app > smoke-test-logs/function-app.log 2>&1 || true
docker logs azurite-smoketest > smoke-test-logs/azurite.log 2>&1 || true
- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: dep-validation-smoke-test-logs
path: smoke-test-logs/
if-no-files-found: ignore
# ---- Cleanup ----
- name: Cleanup
if: always()
run: |
docker stop dep-validation-smoketest-app 2>/dev/null || true
docker rm dep-validation-smoketest-app 2>/dev/null || true
docker stop azurite-smoketest 2>/dev/null || true
docker rm azurite-smoketest 2>/dev/null || true
docker network rm smoketest-network 2>/dev/null || true