Skip to content

Commit a2db89a

Browse files
Add daily extension builds to storage
Mirrors the core CLI daily build pattern. On CI builds (push to main), sign and upload extension binaries to Azure Storage. - publish-extension-daily.yml — new stage, depends on Sign, uploads to {ext-id}/daily (latest) and {ext-id}/daily/archive/{version} (history) - publish-extension.yml — add CreateGitHubRelease param (default true) so daily can skip GitHub Release and just upload to storage - release-azd-extension.yml — wire daily publish for CI builds - update-daily-registry.yml — download existing registry-daily.json from storage, merge in the current extension entry with checksums and storage URLs, upload back. Self-maintaining across extension builds. Closes #7317 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6ac724d commit a2db89a

4 files changed

Lines changed: 259 additions & 44 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
parameters:
2+
- name: SanitizedExtensionId
3+
type: string
4+
- name: AzdExtensionId
5+
type: string
6+
7+
stages:
8+
- stage: PublishDaily
9+
dependsOn: Sign
10+
condition: >-
11+
and(
12+
succeeded(),
13+
ne(variables['Skip.Release'], 'true'),
14+
or(
15+
in(variables['BuildReasonOverride'], 'IndividualCI', 'BatchedCI'),
16+
and(
17+
eq('', variables['BuildReasonOverride']),
18+
in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI')
19+
)
20+
)
21+
)
22+
23+
variables:
24+
- template: /eng/pipelines/templates/variables/image.yml
25+
- template: /eng/pipelines/templates/variables/globals.yml
26+
27+
jobs:
28+
- deployment: Publish_Daily
29+
environment: none
30+
31+
pool:
32+
name: azsdk-pool
33+
image: ubuntu-22.04
34+
os: linux
35+
36+
templateContext:
37+
type: releaseJob
38+
isProduction: false
39+
inputs:
40+
- input: pipelineArtifact
41+
artifactName: release
42+
targetPath: release
43+
44+
strategy:
45+
runOnce:
46+
deploy:
47+
steps:
48+
- template: /eng/pipelines/templates/steps/extension-set-metadata-variables.yml
49+
parameters:
50+
Use1ESArtifactTask: true
51+
52+
- template: /eng/pipelines/templates/steps/publish-extension.yml
53+
parameters:
54+
PublishUploadLocations: ${{ parameters.SanitizedExtensionId }}/daily;${{ parameters.SanitizedExtensionId }}/daily/archive/$(EXT_VERSION)
55+
CreateGitHubRelease: false
56+
57+
- template: /eng/pipelines/templates/steps/update-daily-registry.yml
58+
parameters:
59+
SanitizedExtensionId: ${{ parameters.SanitizedExtensionId }}
60+
AzdExtensionId: ${{ parameters.AzdExtensionId }}

eng/pipelines/templates/stages/release-azd-extension.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,14 @@ stages:
8484
- template: /eng/pipelines/templates/stages/publish-extension.yml
8585
parameters:
8686
SanitizedExtensionId: ${{ parameters.SanitizedExtensionId }}
87+
88+
# Publish daily builds to storage on CI (push to main)
89+
- ${{ if and(eq(variables['System.TeamProject'], 'internal'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI')) }}:
90+
- template: /eng/pipelines/templates/stages/sign-extension.yml
91+
parameters:
92+
SanitizedExtensionId: ${{ parameters.SanitizedExtensionId }}
93+
94+
- template: /eng/pipelines/templates/stages/publish-extension-daily.yml
95+
parameters:
96+
SanitizedExtensionId: ${{ parameters.SanitizedExtensionId }}
97+
AzdExtensionId: ${{ parameters.AzdExtensionId }}

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

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,63 +6,78 @@ parameters:
66
default: '`$web'
77
- name: TagPrefix
88
type: string
9+
default: ''
910
- name: TagVersion
1011
type: string
12+
default: ''
13+
- name: CreateGitHubRelease
14+
type: boolean
15+
default: true
1116

1217
steps:
13-
# This step must run first because a duplicated tag means we don't need to
14-
# continue with any of the subsequent steps.
15-
- pwsh: |
16-
$tag = "${{ parameters.TagPrefix }}_${{ parameters.TagVersion}}"
17-
Write-Host "Release tag: $tag"
18+
- ${{ if eq(parameters.CreateGitHubRelease, true) }}:
19+
# This step must run first because a duplicated tag means we don't need to
20+
# continue with any of the subsequent steps.
21+
- pwsh: |
22+
$tag = "${{ parameters.TagPrefix }}_${{ parameters.TagVersion}}"
23+
Write-Host "Release tag: $tag"
1824
19-
# Check for tag using gh API
20-
$existingTag = gh api /repos/$(Build.Repository.Name)/tags | ConvertFrom-Json | Where-Object { $_.name -eq $tag }
21-
if ($existingTag) {
22-
Write-Host "Tag $tag already exists. Exiting."
25+
# Check for tag using gh API
26+
$existingTag = gh api /repos/$(Build.Repository.Name)/tags | ConvertFrom-Json | Where-Object { $_.name -eq $tag }
27+
if ($existingTag) {
28+
Write-Host "Tag $tag already exists. Exiting."
29+
exit 1
30+
}
31+
32+
gh release view $tag --repo $(Build.Repository.Name)
33+
if ($LASTEXITCODE -eq 0) {
34+
Write-Host "Release ($tag) already exists. Exiting."
2335
exit 1
24-
}
36+
}
2537
26-
gh release view $tag --repo $(Build.Repository.Name)
27-
if ($LASTEXITCODE -eq 0) {
28-
Write-Host "Release ($tag) already exists. Exiting."
29-
exit 1
30-
}
38+
Write-Host "##vso[task.setvariable variable=GH_RELEASE_TAG;]$tag"
3139
32-
Write-Host "##vso[task.setvariable variable=GH_RELEASE_TAG;]$tag"
40+
# Exit with 0 (otherwise $LASTEXITCODE will not be 0 and the pipeline
41+
# will fail)
42+
exit 0
43+
displayName: Check for existing GitHub release
44+
env:
45+
GH_TOKEN: $(azuresdk-github-pat)
3346
34-
# Exit with 0 (otherwise $LASTEXITCODE will not be 0 and the pipeline
35-
# will fail)
36-
exit 0
37-
displayName: Check for existing GitHub release
38-
env:
39-
GH_TOKEN: $(azuresdk-github-pat)
47+
- pwsh: |
48+
Remove-Item -Path release/_manifest -Recurse -Force
49+
Write-Host "Release:"
50+
Get-ChildItem -Recurse release/ | Select-Object -Property Length,FullName
51+
displayName: Remove _manifest folder
4052
41-
- pwsh: |
42-
Remove-Item -Path release/_manifest -Recurse -Force
43-
Write-Host "Release:"
44-
Get-ChildItem -Recurse release/ | Select-Object -Property Length,FullName
45-
displayName: Remove _manifest folder
53+
- pwsh: |
54+
$version = "${{ parameters.TagVersion }}"
55+
$createArgs = @(
56+
"$(GH_RELEASE_TAG)",
57+
"--title", "$(GH_RELEASE_TAG)",
58+
"--notes-file", "changelog/CHANGELOG.md",
59+
"--repo", "$(Build.Repository.Name)"
60+
)
4661
47-
- pwsh: |
48-
$version = "${{ parameters.TagVersion }}"
49-
$createArgs = @(
50-
"$(GH_RELEASE_TAG)",
51-
"--title", "$(GH_RELEASE_TAG)",
52-
"--notes-file", "changelog/CHANGELOG.md",
53-
"--repo", "$(Build.Repository.Name)"
54-
)
62+
if ($version -match "^0\." -or $version -match "-(alpha|beta|preview)") {
63+
$createArgs += "--prerelease"
64+
}
5565
56-
if ($version -match "^0\." -or $version -match "-(alpha|beta|preview)") {
57-
$createArgs += "--prerelease"
58-
}
66+
gh release create @createArgs
5967
60-
gh release create @createArgs
68+
gh release upload $(GH_RELEASE_TAG) release/* --repo $(Build.Repository.Name)
69+
displayName: Create GitHub Release and upload artifacts
70+
env:
71+
GH_TOKEN: $(azuresdk-github-pat)
6172
62-
gh release upload $(GH_RELEASE_TAG) release/* --repo $(Build.Repository.Name)
63-
displayName: Create GitHub Release and upload artifacts
64-
env:
65-
GH_TOKEN: $(azuresdk-github-pat)
73+
- ${{ if eq(parameters.CreateGitHubRelease, false) }}:
74+
- pwsh: |
75+
if (Test-Path release/_manifest) {
76+
Remove-Item -Path release/_manifest -Recurse -Force
77+
}
78+
Write-Host "Release:"
79+
Get-ChildItem -Recurse release/ | Select-Object -Property Length,FullName
80+
displayName: Remove _manifest folder
6681
6782
- task: AzurePowerShell@5
6883
displayName: Upload release to storage account
@@ -77,7 +92,7 @@ steps:
7792
Get-ChildItem release/
7893
foreach ($folder in $uploadLocations) {
7994
Write-Host "Upload to ${{ parameters.StorageContainerName }}/azd/extensions/$folder"
80-
azcopy copy "release/*" "$(publish-storage-location)/${{ parameters.StorageContainerName }}/azd/extensions/$folder"
95+
azcopy copy "release/*" "$(publish-storage-location)/${{ parameters.StorageContainerName }}/azd/extensions/$folder" --overwrite=true
8196
if ($LASTEXITCODE) {
8297
Write-Error "Upload failed"
8398
exit 1
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
parameters:
2+
- name: SanitizedExtensionId
3+
type: string
4+
- name: AzdExtensionId
5+
type: string
6+
7+
steps:
8+
- task: AzurePowerShell@5
9+
displayName: Update daily registry
10+
inputs:
11+
azureSubscription: 'Azure SDK Artifacts'
12+
azurePowerShellVersion: LatestVersion
13+
pwsh: true
14+
ScriptType: InlineScript
15+
Inline: |
16+
$extId = "${{ parameters.AzdExtensionId }}"
17+
$sanitizedId = "${{ parameters.SanitizedExtensionId }}"
18+
$version = "$(EXT_VERSION)"
19+
$storageHost = "$(publish-storage-static-host)"
20+
$registryBlobPath = "$(publish-storage-location)/`$web/azd/extensions/registry-daily.json"
21+
$dailyBaseUrl = "$storageHost/azd/extensions/$sanitizedId/daily"
22+
23+
# Download existing registry or create empty one
24+
$registryFile = "registry-daily.json"
25+
azcopy copy $registryBlobPath $registryFile 2>$null
26+
if ($LASTEXITCODE -ne 0 -or !(Test-Path $registryFile)) {
27+
Write-Host "No existing registry found, creating new one"
28+
@{ extensions = @() } | ConvertTo-Json -Depth 10 | Set-Content $registryFile
29+
}
30+
31+
$registry = Get-Content $registryFile -Raw | ConvertFrom-Json -Depth 20
32+
33+
# Read extension.yaml from the release-metadata artifact
34+
$extYaml = Get-Content "release-metadata/extension.yaml" -Raw
35+
$extMeta = @{}
36+
foreach ($line in $extYaml -split "`n") {
37+
if ($line -match "^(\w[\w\-]*):\s*(.+)$") {
38+
$extMeta[$matches[1]] = $matches[2].Trim()
39+
}
40+
}
41+
42+
# Parse list fields from extension.yaml
43+
function Get-YamlList($yaml, $field) {
44+
$items = @()
45+
$inField = $false
46+
foreach ($line in $yaml -split "`n") {
47+
if ($line -match "^${field}:") { $inField = $true; continue }
48+
if ($inField -and $line -match "^\s+-\s+(.+)$") {
49+
$items += $matches[1].Trim()
50+
} elseif ($inField -and $line -match "^\S") {
51+
break
52+
}
53+
}
54+
return $items
55+
}
56+
57+
$capabilities = Get-YamlList $extYaml "capabilities"
58+
59+
# Compute checksums from the release artifacts
60+
$platforms = @(
61+
@{ key = "darwin/amd64"; file = "$sanitizedId-darwin-amd64.zip"; entry = "$sanitizedId-darwin-amd64" },
62+
@{ key = "darwin/arm64"; file = "$sanitizedId-darwin-arm64.zip"; entry = "$sanitizedId-darwin-arm64" },
63+
@{ key = "linux/amd64"; file = "$sanitizedId-linux-amd64.tar.gz"; entry = "$sanitizedId-linux-amd64" },
64+
@{ key = "linux/arm64"; file = "$sanitizedId-linux-arm64.tar.gz"; entry = "$sanitizedId-linux-arm64" },
65+
@{ key = "windows/amd64"; file = "$sanitizedId-windows-amd64.zip"; entry = "$sanitizedId-windows-amd64.exe" },
66+
@{ key = "windows/arm64"; file = "$sanitizedId-windows-arm64.zip"; entry = "$sanitizedId-windows-arm64.exe" }
67+
)
68+
69+
$artifacts = @{}
70+
foreach ($p in $platforms) {
71+
$filePath = "release/$($p.file)"
72+
if (Test-Path $filePath) {
73+
$hash = (Get-FileHash -Path $filePath -Algorithm SHA256).Hash.ToLower()
74+
$artifacts[$p.key] = @{
75+
checksum = @{ algorithm = "sha256"; value = $hash }
76+
entryPoint = $p.entry
77+
url = "$dailyBaseUrl/$($p.file)"
78+
}
79+
}
80+
}
81+
82+
# Build the version entry
83+
$versionEntry = @{
84+
version = $version
85+
capabilities = $capabilities
86+
artifacts = $artifacts
87+
}
88+
89+
if ($extMeta["usage"]) { $versionEntry.usage = $extMeta["usage"] }
90+
if ($extMeta["requiredAzdVersion"]) {
91+
$versionEntry.requiredAzdVersion = $extMeta["requiredAzdVersion"]
92+
}
93+
94+
# Build the extension entry
95+
$extEntry = @{
96+
id = $extId
97+
namespace = $extMeta["namespace"]
98+
displayName = $extMeta["displayName"]
99+
description = $extMeta["description"]
100+
versions = @($versionEntry)
101+
}
102+
103+
# Merge into registry: replace if extension exists, add if new
104+
$found = $false
105+
for ($i = 0; $i -lt $registry.extensions.Count; $i++) {
106+
if ($registry.extensions[$i].id -eq $extId) {
107+
$registry.extensions[$i] = $extEntry
108+
$found = $true
109+
break
110+
}
111+
}
112+
if (-not $found) {
113+
$registry.extensions += $extEntry
114+
}
115+
116+
# Write and upload
117+
$registry | ConvertTo-Json -Depth 20 | Set-Content $registryFile -Encoding utf8
118+
Write-Host "Updated registry for $extId ($version):"
119+
Get-Content $registryFile
120+
121+
azcopy copy $registryFile $registryBlobPath --overwrite=true
122+
if ($LASTEXITCODE) {
123+
Write-Error "Failed to upload registry"
124+
exit 1
125+
}
126+
127+
Write-Host "Registry uploaded to $registryBlobPath"
128+
env:
129+
AZCOPY_AUTO_LOGIN_TYPE: 'PSCRED'

0 commit comments

Comments
 (0)