Skip to content

Commit fe4304a

Browse files
Address PR review feedback: powershell-yaml, registry format, error handling
- Replace regex YAML parsing with powershell-yaml for proper parsing - Wrap JSON output in { extensions: [...] } registry format for azd compatibility - Move template to eng/templates/, copy scripts into release-metadata artifact - Flip CreateGitHubRelease default to false, explicit true in release caller - Add idempotency guard in version script for pipeline retries - Add placeholder validation, null checks, and file-exists guards - Split copy steps for clearer error attribution in CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b3e211d commit fe4304a

8 files changed

Lines changed: 76 additions & 44 deletions

eng/pipelines/templates/stages/build-and-test-azd-extension.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ stages:
161161
Copy-Item NOTICE.txt release-metadata/NOTICE.txt
162162
displayName: Copy NOTICE.txt to release-metadata
163163
164+
- pwsh: |
165+
Copy-Item eng/templates/extensions-registry-daily.json.template release-metadata/extensions-registry-daily.json.template
166+
displayName: Copy registry template to release-metadata
167+
168+
- pwsh: |
169+
Copy-Item eng/scripts/Update-ExtensionDailyRegistry.ps1 release-metadata/Update-ExtensionDailyRegistry.ps1
170+
displayName: Copy Update-ExtensionDailyRegistry.ps1 to release-metadata
171+
172+
- pwsh: |
173+
Copy-Item eng/common/scripts/Helpers/PSModule-Helpers.ps1 release-metadata/PSModule-Helpers.ps1
174+
displayName: Copy PSModule-Helpers.ps1 to release-metadata
175+
164176
templateContext:
165177
outputs:
166178
- output: pipelineArtifact

eng/pipelines/templates/stages/publish-extension-daily.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ stages:
4040
- input: pipelineArtifact
4141
artifactName: release
4242
targetPath: release
43+
- input: pipelineArtifact
44+
artifactName: release-metadata
45+
targetPath: release-metadata
4346

4447
strategy:
4548
runOnce:

eng/pipelines/templates/stages/publish-extension.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,6 @@ stages:
6767
- template: /eng/pipelines/templates/steps/publish-extension.yml
6868
parameters:
6969
PublishUploadLocations: $(StorageUploadLocations)
70+
CreateGitHubRelease: true
7071
TagPrefix: azd-ext-${{ parameters.SanitizedExtensionId }}
7172
TagVersion: $(EXT_VERSION)

eng/pipelines/templates/steps/publish-extension.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ parameters:
1212
default: ''
1313
- name: CreateGitHubRelease
1414
type: boolean
15-
default: true
15+
default: false
1616

1717
steps:
1818
# Remove _manifest folder unconditionally before upload/release

eng/pipelines/templates/steps/update-extension-daily-registry.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ steps:
1616
$storageHost = "$(publish-storage-static-host)"
1717
$dailyBaseUrl = "$storageHost/azd/extensions/${{ parameters.SanitizedExtensionId }}/daily"
1818
$entryBlobPath = "$(publish-storage-location)/`$web/azd/extensions/daily-registry-entries/${{ parameters.AzdExtensionId }}.json"
19-
$templatePath = "$(Build.SourcesDirectory)/eng/pipelines/templates/json/extension-registry-daily-template.json"
19+
$templatePath = "release-metadata/extensions-registry-daily.json.template"
2020
21-
& "$(Build.SourcesDirectory)/eng/scripts/Update-ExtensionDailyRegistry.ps1" `
21+
& "release-metadata/Update-ExtensionDailyRegistry.ps1" `
2222
-SanitizedExtensionId "${{ parameters.SanitizedExtensionId }}" `
2323
-AzdExtensionId "${{ parameters.AzdExtensionId }}" `
2424
-Version "$(EXT_VERSION)" `
2525
-StorageBaseUrl $dailyBaseUrl `
2626
-RegistryEntryBlobPath $entryBlobPath `
27-
-TemplatePath $templatePath
27+
-TemplatePath $templatePath `
28+
-ReleasePath "release" `
29+
-MetadataPath "release-metadata"
2830
env:
2931
AZCOPY_AUTO_LOGIN_TYPE: 'PSCRED'

eng/scripts/Set-ExtensionVersionInBuild.ps1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,22 @@ else {
3434
}
3535

3636
$versionFile = Join-Path $ExtensionDirectory "version.txt"
37+
if (!(Test-Path $versionFile)) {
38+
Write-Error "version.txt not found at $versionFile"
39+
exit 1
40+
}
3741
$version = (Get-Content $versionFile).Trim()
3842
if ([string]::IsNullOrWhiteSpace($version)) {
3943
Write-Error "version.txt is empty at $versionFile"
4044
exit 1
4145
}
46+
47+
# Guard against pipeline retries — skip if suffix already applied
48+
if ($version -match "-${prereleaseCategory}\.\d+$") {
49+
Write-Host "Version '$version' already has $prereleaseCategory suffix, skipping."
50+
exit 0
51+
}
52+
4253
$newVersion = "$version-$prereleaseCategory.$BuildId"
4354

4455
Set-Content $versionFile -Value $newVersion

eng/scripts/Update-ExtensionDailyRegistry.ps1

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
4. Uploads the entry as a standalone per-extension JSON blob
1010
1111
Each extension writes its own file to avoid race conditions when multiple
12-
extension pipelines run concurrently. A separate unification script
13-
(Build-UnifiedDailyRegistry.ps1) combines all per-extension entries into
14-
the final registry-daily.json.
12+
extension pipelines run concurrently. The azd CLI reads each per-extension
13+
entry directly via the registry source URL.
1514
1615
.PARAMETER SanitizedExtensionId
1716
Hyphenated extension id (e.g. azure-ai-agents)
@@ -96,48 +95,43 @@ if ($missingArtifacts.Count -gt 0) {
9695
exit 1
9796
}
9897

99-
# Read extension.yaml for metadata.
100-
# Uses simple line-by-line regex parsing — handles top-level scalar fields,
101-
# capabilities list, and providers list. This is intentionally not a full YAML
102-
# parser. It works for the known extension.yaml schema where all values are
103-
# single-line scalars or simple lists. If extension.yaml grows multi-line
104-
# values or complex nesting, switch to powershell-yaml.
105-
$extYaml = Get-Content $extYamlPath -Raw
106-
$extMeta = @{}
107-
foreach ($line in $extYaml -split "`n") {
108-
if ($line -match "^(\w[\w\-]*):\s*(.+)$") {
109-
$extMeta[$matches[1]] = $matches[2].Trim().Trim('"')
110-
}
98+
# Install powershell-yaml for proper YAML parsing
99+
$psModuleHelpers = Join-Path $PSScriptRoot "PSModule-Helpers.ps1"
100+
if (!(Test-Path $psModuleHelpers)) {
101+
# Fallback to repo location when running from source checkout
102+
$psModuleHelpers = Join-Path $PSScriptRoot "../common/scripts/Helpers/PSModule-Helpers.ps1"
103+
}
104+
if (!(Test-Path $psModuleHelpers)) {
105+
Write-Error "PSModule-Helpers.ps1 not found at $PSScriptRoot or repo fallback path"
106+
exit 1
107+
}
108+
. $psModuleHelpers
109+
Install-ModuleIfNotInstalled "powershell-yaml" "0.4.7" | Import-Module
110+
111+
# Parse extension.yaml
112+
$extData = ConvertFrom-Yaml (Get-Content $extYamlPath -Raw)
113+
if ($null -eq $extData) {
114+
Write-Error "Failed to parse extension.yaml at $extYamlPath — file may be empty or malformed"
115+
exit 1
111116
}
112117

113-
# Parse capabilities list
114-
$capabilities = @()
115-
$inCapabilities = $false
116-
foreach ($line in $extYaml -split "`n") {
117-
if ($line -match "^capabilities:") { $inCapabilities = $true; continue }
118-
if ($inCapabilities -and $line -match "^\s+-\s+(.+)$") {
119-
$capabilities += $matches[1].Trim()
120-
} elseif ($inCapabilities -and $line -match "^\S") {
121-
break
118+
$extMeta = @{}
119+
foreach ($key in @('namespace', 'displayName', 'description', 'usage', 'requiredAzdVersion')) {
120+
if ($extData.ContainsKey($key)) {
121+
$extMeta[$key] = $extData[$key]
122122
}
123123
}
124124

125-
# Parse providers list
125+
$capabilities = if ($extData.ContainsKey('capabilities')) { @($extData['capabilities']) } else { @() }
126+
126127
$providers = @()
127-
$inProviders = $false
128-
$currentProvider = $null
129-
foreach ($line in $extYaml -split "`n") {
130-
if ($line -match "^providers:") { $inProviders = $true; continue }
131-
if ($inProviders -and $line -match "^\s+-\s+name:\s*(.+)$") {
132-
if ($currentProvider) { $providers += $currentProvider }
133-
$currentProvider = [ordered]@{ name = $matches[1].Trim() }
134-
} elseif ($inProviders -and $currentProvider -and $line -match "^\s+(\w+):\s*(.+)$") {
135-
$currentProvider[$matches[1].Trim()] = $matches[2].Trim()
136-
} elseif ($inProviders -and $line -match "^\S") {
137-
break
128+
if ($extData.ContainsKey('providers')) {
129+
foreach ($p in $extData['providers']) {
130+
$provider = [ordered]@{}
131+
foreach ($k in $p.Keys) { $provider[$k] = $p[$k] }
132+
$providers += $provider
138133
}
139134
}
140-
if ($currentProvider) { $providers += $currentProvider }
141135

142136
# Validate required fields were parsed
143137
$requiredFields = @('namespace', 'displayName', 'description', 'usage')
@@ -176,6 +170,12 @@ foreach ($placeholder in $replacements.Keys) {
176170
$template = $template.Replace($placeholder, $replacements[$placeholder])
177171
}
178172

173+
# Verify all placeholders were replaced
174+
if ($template -match '\$\{[A-Za-z0-9_]+\}') {
175+
Write-Error "Unreplaced placeholder found: $($matches[0])"
176+
exit 1
177+
}
178+
179179
$versionEntry = $template | ConvertFrom-Json
180180

181181
# Add capabilities and providers (can't template arrays/objects easily)
@@ -184,7 +184,8 @@ if ($providers.Count -gt 0) {
184184
$versionEntry | Add-Member -NotePropertyName "providers" -NotePropertyValue $providers
185185
}
186186

187-
# Build the per-extension entry
187+
# Build the per-extension entry wrapped in registry format
188+
# azd ext source add -t url expects { "extensions": [...] }
188189
$extEntry = [ordered]@{
189190
id = $AzdExtensionId
190191
namespace = $extMeta.namespace
@@ -193,9 +194,11 @@ $extEntry = [ordered]@{
193194
versions = @($versionEntry)
194195
}
195196

196-
# Write per-extension entry and validate JSON
197+
$registry = [ordered]@{ extensions = @($extEntry) }
198+
199+
# Write registry entry and validate JSON
197200
$entryFile = "$AzdExtensionId.json"
198-
$extEntry | ConvertTo-Json -Depth 20 | Set-Content $entryFile -Encoding utf8
201+
$registry | ConvertTo-Json -Depth 20 | Set-Content $entryFile -Encoding utf8
199202

200203
try {
201204
$null = Get-Content $entryFile -Raw | ConvertFrom-Json -Depth 20

eng/pipelines/templates/json/extension-registry-daily-template.json renamed to eng/templates/extensions-registry-daily.json.template

File renamed without changes.

0 commit comments

Comments
 (0)