diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index ffcb7ff8..2e9a6a3f 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -2,8 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) variables: - name: Codeql.Enabled value: true - - name: npm_config_userconfig - value: $(Agent.TempDirectory)/.npmrc + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self diff --git a/.azure-pipelines/nightly.yml b/.azure-pipelines/nightly.yml index d50ba829..97ff7678 100644 --- a/.azure-pipelines/nightly.yml +++ b/.azure-pipelines/nightly.yml @@ -2,8 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) variables: - name: Codeql.Enabled value: true - - name: npm_config_userconfig - value: $(Agent.TempDirectory)/.npmrc + - template: /.azure-pipelines/npm-cfs-variables.yml@self schedules: - cron: 0 0 * * * branches: diff --git a/.azure-pipelines/npm-cfs-variables.yml b/.azure-pipelines/npm-cfs-variables.yml new file mode 100644 index 00000000..d4d17634 --- /dev/null +++ b/.azure-pipelines/npm-cfs-variables.yml @@ -0,0 +1,28 @@ +# Variables required to route npm package restore through the Central Feed Service +# (CFS). Consumed by every pipeline in this directory alongside the npm-cfs.yml steps +# template, which is where these values are actually applied. +# +# Both are declared here rather than in each pipeline so the feed URL exists in +# exactly one place. +# +# npm_config_registry is not redundant with the registry written into the generated +# .npmrc. npm resolves configuration in the order cli > environment > project .npmrc +# > user .npmrc, so a registry supplied only through the user config is outranked by +# anything the agent image already configures -- Microsoft hosted images ship a user +# level .npmrc pointing at an internal proxy, and a pool that exports +# npm_config_registry would win outright. Restore would then quietly resolve from +# somewhere other than CFS while the build still reported success. Declaring the +# variable here puts the redirect at environment precedence, where only an explicit +# command line flag can override it. +# +# npm matches npm_config_* environment variables case insensitively, so the +# uppercased form that Azure Pipelines exports applies to every step on every OS. +# That matters because package restore here is not driven by a single task: `npm +# install`, `npx json`, `npx @vscode/vsce` and the vsce invocation inside AzureCLI@2 +# all inherit the agent environment rather than reading a task input. + +variables: + - name: npm_config_registry + value: https://pkgs.dev.azure.com/mseng/VSJava/_packaging/vscjava/npm/registry/ + - name: npm_config_userconfig + value: $(Agent.TempDirectory)/.npmrc diff --git a/.azure-pipelines/npm-cfs.yml b/.azure-pipelines/npm-cfs.yml index 81939ddd..cfb955af 100644 --- a/.azure-pipelines/npm-cfs.yml +++ b/.azure-pipelines/npm-cfs.yml @@ -1,6 +1,12 @@ -# Single source of truth for routing npm package restore through the Central Feed -# Service (CFS), as required by SFI Network Isolation. Every build pipeline in this -# directory consumes this template, so the feed URL is declared exactly once. +# Routes npm package restore through the Central Feed Service (CFS), as required by +# SFI Network Isolation. Consumed by every build pipeline in this directory. +# +# Pipelines must also include the companion variables template: +# variables: +# - template: /.azure-pipelines/npm-cfs-variables.yml@self +# which declares the feed URL and the generated .npmrc path. The redirect itself is +# carried by the npm_config_registry environment variable that template exports; see +# its header for why the generated .npmrc alone is not enough. # # The .npmrc is generated at build time into the agent temp directory rather than # being committed to the repository, so that: @@ -11,10 +17,9 @@ # * the configuration does not depend on the repository being checked out, so # release jobs consuming a prebuilt artifact work the same way as build jobs. # -# Pipelines consuming this template must also declare the pipeline level variable -# npm_config_userconfig: $(Agent.TempDirectory)/.npmrc -# npm matches npm_config_* environment variables case insensitively, so the -# uppercased form that Azure Pipelines exports applies to every step on every OS. +# The registry is still written into that file because NpmAuthenticate discovers the +# registries to authenticate by reading it. npm then takes the URL from the +# environment and the matching credential from this file. # # The file is written with `npm config set` rather than a shell redirect because the # pipelines span both Windows (VSEng-MicroBuildVSStable) and Linux @@ -26,14 +31,14 @@ # This template must run after the Node install task, and before any step that # restores packages -- including `npx`, which resolves downloads through the # configured registry. - -parameters: - - name: npmrcPath - type: string - default: $(Agent.TempDirectory)/.npmrc +# +# Consumers must reference this file as `/.azure-pipelines/npm-cfs.yml@self`. A +# relative path is resolved against the file doing the including, which for these +# pipelines is the 1ES extends template in another repository, so the unqualified +# form is looked up in 1ESPipelineTemplates and fails YAML compilation. steps: - - script: npm config set registry https://pkgs.dev.azure.com/mseng/VSJava/_packaging/vscjava/npm/registry/ --location=user --userconfig="${{ parameters.npmrcPath }}" + - script: npm config set registry $(npm_config_registry) --location=user --userconfig="$(npm_config_userconfig)" displayName: Configure CFS npm registry # Appends `//pkgs.dev.azure.com/.../registry/:_authToken=` for every registry @@ -42,4 +47,13 @@ steps: - task: NpmAuthenticate@0 displayName: Authenticate to CFS feed inputs: - workingFile: ${{ parameters.npmrcPath }} + workingFile: $(npm_config_userconfig) + + # Restore silently falling back to the public registry is the failure mode this + # whole template exists to prevent, and it leaves no trace in the build log, so it + # is asserted rather than assumed. Written in node, which the preceding Node + # install task guarantees, to avoid shell differences between the Linux and + # Windows pools. + - script: >- + node -e "const cp=require('child_process');const r=cp.execSync('npm config get registry').toString().trim();console.log('npm registry -> '+r);if(!r.startsWith('https://pkgs.dev.azure.com/')){console.error('##vso[task.logissue type=error]npm is not configured against the CFS feed');process.exit(1);}" + displayName: Verify CFS npm registry diff --git a/.azure-pipelines/rc.yml b/.azure-pipelines/rc.yml index 57193d6e..101ade0d 100644 --- a/.azure-pipelines/rc.yml +++ b/.azure-pipelines/rc.yml @@ -2,8 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) variables: - name: Codeql.Enabled value: true - - name: npm_config_userconfig - value: $(Agent.TempDirectory)/.npmrc + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self diff --git a/.azure-pipelines/release-nightly.yml b/.azure-pipelines/release-nightly.yml index 3cd0bb1f..bbd3f446 100644 --- a/.azure-pipelines/release-nightly.yml +++ b/.azure-pipelines/release-nightly.yml @@ -8,8 +8,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) # Use the current date and a revision number for variables: - name: Codeql.Enabled value: true - - name: npm_config_userconfig - value: $(Agent.TempDirectory)/.npmrc + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 89668b71..b4b58f64 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -8,8 +8,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) # Use the current date and a revision number for variables: - name: Codeql.Enabled value: true - - name: npm_config_userconfig - value: $(Agent.TempDirectory)/.npmrc + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self