From b2888cbdac387074b0f8c319efe680d699e5745c Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Mon, 13 Apr 2026 22:13:02 -0700 Subject: [PATCH 1/3] Split CD pipeline into Check + Package stages to avoid 1ES overhead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The hourly CD pipeline took ~30 minutes even when no deployment was needed. The 1ES Official Pipeline Template injects SDL/compliance tasks (CredScan, PoliCheck, BinSkim, Windows source analysis agent) **at the stage level**. With everything in a single `Package` stage, these tasks ran every hour regardless of whether the `Deploy` job was skipped. ## Root cause ```yaml # Before: one stage, two jobs - stage: Package jobs: - job: NeedsDeployment # ~1 min check - job: Deploy # conditional, often skipped # But 1ES SDL tasks still run for the stage → ~30 min overhead ``` The `Deploy` job condition correctly evaluated to `false`, but the stage-level SDL overhead (Windows agent provisioning, compliance scans) still executed every run. ## Fix Split into two stages so the heavyweight stage is **entirely skipped** when no deployment is needed: ```yaml # After: two stages - stage: Check # Lightweight version check (~1-2 min) jobs: - job: CheckVersion # Downloads release, checks npm + crates.io - stage: Package # Only runs when needsDeployment == true dependsOn: Check condition: eq(dependencies.Check.outputs[...], 'true') jobs: - job: Deploy # Downloads artifacts and publishes ``` When `Package` is skipped at the stage level, Azure Pipelines does not provision agents or run any 1ES-injected SDL tasks for it. ## Impact Hourly no-op runs drop from **~30 minutes → ~1-2 minutes**. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-pipelines-cd.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/azure-pipelines-cd.yml b/azure-pipelines-cd.yml index ee6fab95..5955ecea 100644 --- a/azure-pipelines-cd.yml +++ b/azure-pipelines-cd.yml @@ -41,12 +41,14 @@ extends: networkIsolationPolicy: Permissive stages: - # ── Download pre-built release artifacts and deploy ── - - stage: Package + # ── Stage 1: Lightweight version check (runs every hour, ~1-2 min) ── + # This stage is separate so that when no deployment is needed, the + # heavyweight Package stage (with all its 1ES SDL tasks) is skipped + # entirely — avoiding ~30 min of unnecessary agent provisioning and + # compliance scans on every hourly no-op run. + - stage: Check jobs: - - # ── Job 1: Resolve the latest release tag and check registry status ── - - job: NeedsDeployment + - job: CheckVersion steps: - checkout: self persistCredentials: "true" @@ -60,8 +62,7 @@ extends: downloadPath: '$(System.ArtifactsDirectory)' # Extract the version number from the downloaded .crate filename, - # e.g. microsoft-webui-0.0.3.crate → 0.0.3, and expose it as output - # variables for the Deploy job. + # e.g. microsoft-webui-0.0.3.crate → 0.0.3 - script: | CRATE_FILE=$(ls "$(System.ArtifactsDirectory)"/microsoft-webui-*.crate 2>/dev/null | head -1) if [ -z "$CRATE_FILE" ]; then @@ -106,12 +107,14 @@ extends: displayName: Check if already deployed name: deploymentCheck - # ── Job 2: Download artifacts and deploy (skipped if all already deployed) ── + # ── Stage 2: Download artifacts and deploy (entirely skipped if already deployed) ── + - stage: Package + dependsOn: Check + condition: eq(dependencies.Check.outputs['CheckVersion.deploymentCheck.needsDeployment'], 'true') + variables: + releaseTag: $[ stageDependencies.Check.CheckVersion.outputs['resolveTag.releaseTag'] ] + jobs: - job: Deploy - dependsOn: NeedsDeployment - condition: eq(dependencies.NeedsDeployment.outputs['deploymentCheck.needsDeployment'], 'true') - variables: - releaseTag: $[ dependencies.NeedsDeployment.outputs['resolveTag.releaseTag'] ] steps: - checkout: self persistCredentials: "true" From aba5876f75be805116aa0640299588240a5b1939 Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Mon, 13 Apr 2026 22:46:08 -0700 Subject: [PATCH 2/3] Fix false deployment triggers from network errors in registry checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The registry checks falsely reported versions as "not yet deployed" when the 1ES agent couldn't reach the registries: - **npm**: Used `npm view ... 2>/dev/null` which silently swallows all errors. If npm isn't installed or the registry is unreachable, the empty output is treated as "not deployed". - **crates.io**: `curl` returned `HTTP 000` (connection failure — DNS/proxy/firewall), and any non-200 was treated as "not deployed". Both caused unnecessary deployment runs against already-published versions. ## Fix ```bash # Before (npm) — npm CLI dependency, errors silently swallowed npm view "@microsoft/webui@${VERSION}" version 2>/dev/null | grep -qxF "${VERSION}" # After (npm) — curl to registry HTTP API, no npm CLI needed NPM_STATUS=$(check_registry "npm" "https://registry.npmjs.org/@microsoft%2Fwebui/${VERSION}") ``` ```bash # Before (crates.io) — no timeout, no retry, HTTP 000 = "not deployed" curl -s -o /dev/null -w "%{http_code}" ... # After (both) — shared helper with timeout, retry, and network error detection check_registry() { curl ... --connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 ... if [ "$status" = "000" ]; then exit 1; fi # fail, don't falsely trigger } ``` Key changes: - Replaced `npm view` with direct curl to `registry.npmjs.org` HTTP API - Added `--connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5` - HTTP 000 now **fails the step** with an actionable error instead of silently triggering a deployment - Added `set -euo pipefail` for strict error handling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-pipelines-cd.yml | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/azure-pipelines-cd.yml b/azure-pipelines-cd.yml index 5955ecea..45867322 100644 --- a/azure-pipelines-cd.yml +++ b/azure-pipelines-cd.yml @@ -80,22 +80,40 @@ extends: # Check whether this version is already deployed to all registries. # Sets needsDeployment=true if any of npm or crates.io do not yet carry # the version — so a partial deployment still reruns the template. + # Uses curl for both checks (no npm CLI dependency) with retries and + # timeouts. Fails on network errors to avoid false deployment triggers. - script: | + set -euo pipefail VERSION="$(releaseVersion)" NEEDS_DEPLOYMENT=false - # npm - if npm view "@microsoft/webui@${VERSION}" version 2>/dev/null | grep -qxF "${VERSION}"; then + # Helper: curl with retry and timeout. Returns HTTP status code. + # Fails the script if the network is unreachable (HTTP 000). + check_registry() { + local name="$1" url="$2" + local status + status=$(curl -s -o /dev/null -w "%{http_code}" \ + --connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \ + -H "User-Agent: webui-cd-pipeline (microsoft/webui)" \ + "$url") + if [ "$status" = "000" ]; then + echo "##vso[task.logissue type=error]Network error checking ${name} (HTTP 000 — could not connect). Check agent proxy/firewall settings." + exit 1 + fi + echo "$status" + } + + # npm (uses the registry HTTP API directly — no npm CLI needed) + NPM_STATUS=$(check_registry "npm" "https://registry.npmjs.org/@microsoft%2Fwebui/${VERSION}") + if [ "$NPM_STATUS" = "200" ]; then echo "npm @microsoft/webui@${VERSION} already deployed" else - echo "npm @microsoft/webui@${VERSION} not yet deployed" + echo "npm @microsoft/webui@${VERSION} not yet deployed (HTTP ${NPM_STATUS})" NEEDS_DEPLOYMENT=true fi # crates.io - CRATE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "User-Agent: webui-cd-pipeline (microsoft/webui)" \ - "https://crates.io/api/v1/crates/microsoft-webui/${VERSION}") + CRATE_STATUS=$(check_registry "crates.io" "https://crates.io/api/v1/crates/microsoft-webui/${VERSION}") if [ "$CRATE_STATUS" = "200" ]; then echo "crates.io microsoft-webui ${VERSION} already deployed" else From cf4bef56d6c8dc74a60c791086b974ceba48da4a Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Mon, 13 Apr 2026 22:48:05 -0700 Subject: [PATCH 3/3] Replace registry HTTP checks with git tag-based deployment tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The 1ES agents cannot reach external registries (npm, crates.io) — curl returns `HTTP 000` (connection failure) due to firewall/proxy restrictions. This caused every hourly run to falsely detect versions as "not yet deployed" and trigger unnecessary deployment attempts. ## Solution Replace external registry checks with git tag-based tracking: ### Check stage — look for existing tag ```bash # Before: unreliable curl to external registries check_registry "npm" "https://registry.npmjs.org/..." check_registry "crates.io" "https://crates.io/api/v1/..." # After: check for deployed/* git tag (no external network needed) git fetch --tags --quiet if git tag -l "deployed/v${VERSION}" | grep -q .; then # already deployed fi ``` ### Deploy job — tag after successful deployment ```bash # New step at end of Deploy job DEPLOY_TAG="deployed/$(releaseTag)" # e.g. deployed/v0.0.8 git tag "${DEPLOY_TAG}" git push origin "${DEPLOY_TAG}" ``` ### How it works 1. Check stage extracts version from .crate filename (e.g. `0.0.8`) 2. Checks if `deployed/v0.0.8` tag exists in the repo 3. If tag exists → `needsDeployment=false` → Package stage skipped 4. If tag missing → `needsDeployment=true` → Package stage runs 5. After successful deploy, pushes `deployed/v0.0.8` tag ### Benefits - Zero external network dependencies — works behind any firewall - Atomic: tag only pushed after the release template succeeds - Auditable: `git tag -l 'deployed/*'` shows full deploy history - Partial deploy recovery: if deploy fails mid-way, no tag is pushed, so the next hourly run retries automatically Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-pipelines-cd.yml | 61 +++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/azure-pipelines-cd.yml b/azure-pipelines-cd.yml index 45867322..00e32ef6 100644 --- a/azure-pipelines-cd.yml +++ b/azure-pipelines-cd.yml @@ -77,51 +77,23 @@ extends: displayName: Extract version from downloaded artifacts name: resolveTag - # Check whether this version is already deployed to all registries. - # Sets needsDeployment=true if any of npm or crates.io do not yet carry - # the version — so a partial deployment still reruns the template. - # Uses curl for both checks (no npm CLI dependency) with retries and - # timeouts. Fails on network errors to avoid false deployment triggers. + # Check whether this version has already been deployed by looking + # for a "deployed/vX.Y.Z" git tag. This avoids external network + # calls to npm/crates.io which are unreachable from 1ES agents. + # The Deploy job pushes this tag after a successful deployment. - script: | set -euo pipefail VERSION="$(releaseVersion)" - NEEDS_DEPLOYMENT=false + DEPLOY_TAG="deployed/v${VERSION}" - # Helper: curl with retry and timeout. Returns HTTP status code. - # Fails the script if the network is unreachable (HTTP 000). - check_registry() { - local name="$1" url="$2" - local status - status=$(curl -s -o /dev/null -w "%{http_code}" \ - --connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \ - -H "User-Agent: webui-cd-pipeline (microsoft/webui)" \ - "$url") - if [ "$status" = "000" ]; then - echo "##vso[task.logissue type=error]Network error checking ${name} (HTTP 000 — could not connect). Check agent proxy/firewall settings." - exit 1 - fi - echo "$status" - } - - # npm (uses the registry HTTP API directly — no npm CLI needed) - NPM_STATUS=$(check_registry "npm" "https://registry.npmjs.org/@microsoft%2Fwebui/${VERSION}") - if [ "$NPM_STATUS" = "200" ]; then - echo "npm @microsoft/webui@${VERSION} already deployed" - else - echo "npm @microsoft/webui@${VERSION} not yet deployed (HTTP ${NPM_STATUS})" - NEEDS_DEPLOYMENT=true - fi - - # crates.io - CRATE_STATUS=$(check_registry "crates.io" "https://crates.io/api/v1/crates/microsoft-webui/${VERSION}") - if [ "$CRATE_STATUS" = "200" ]; then - echo "crates.io microsoft-webui ${VERSION} already deployed" + git fetch --tags --quiet + if git tag -l "${DEPLOY_TAG}" | grep -q .; then + echo "${DEPLOY_TAG} tag exists — version ${VERSION} already deployed" + echo "##vso[task.setvariable variable=needsDeployment;isOutput=true]false" else - echo "crates.io microsoft-webui ${VERSION} not yet deployed (HTTP ${CRATE_STATUS})" - NEEDS_DEPLOYMENT=true + echo "${DEPLOY_TAG} tag not found — version ${VERSION} needs deployment" + echo "##vso[task.setvariable variable=needsDeployment;isOutput=true]true" fi - - echo "##vso[task.setvariable variable=needsDeployment;isOutput=true]${NEEDS_DEPLOYMENT}" displayName: Check if already deployed name: deploymentCheck @@ -158,3 +130,14 @@ extends: displayName: Separate release artifacts - template: WebUI.Release.PipelineTemplate.yml@webuiPipelines # Template reference + + # Mark this version as deployed so future pipeline runs skip it. + # Uses the releaseTag stage variable (e.g. "v0.0.8") to create + # a "deployed/v0.0.8" tag and push it back to the repo. + - script: | + set -euo pipefail + DEPLOY_TAG="deployed/$(releaseTag)" + echo "Tagging successful deployment: ${DEPLOY_TAG}" + git tag "${DEPLOY_TAG}" + git push origin "${DEPLOY_TAG}" + displayName: Tag successful deployment