Example
parameters:
# Branch to reset/replay snapshots on ('main' or 'migration')
- name: branch
type: string
# Target database name for snapshots and registry (simple name like 'demo' without an org/env prefix)
- name: database
type: string
# Platforms with historical snapshots; each includes display name, semantic version, and charset
- name: platforms
type: object
default:
- name: Jade 2022 SP4 (ANSI)
version: 22.0.05
charset: ansi
- name: Jade 2025 R1 (ANSI)
version: 25.0.01
charset: ansi
# Name of the repository to clone and update
- name: repository
type: string
stages:
- stage: ${{ parameters.repository }} # Stage named after repository
dependsOn: [] # Independent stage
jobs:
- job: Migrate # Core migration job
workspace:
clean: all # Ensure a fresh workspace
variables:
# Folder to process snapshots
- name: source
value: $(Pipeline.Workspace)\source
# Local repository path
- name: repo
value: $(Pipeline.Workspace)\repo
timeoutInMinutes: 0 # Disable job timeout
steps:
# Disable automatic checkout; repository will be cloned manually
- checkout: none
# Clone bare repository
- powershell: |
git clone --bare -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" $(migrate.namespace).${{ parameters.repository }} $(repo)
displayName: Clone Repo
# Set author for any commits created during extract
- powershell: |
git config user.name "$(Build.RequestedFor)"
git config user.email "$(Build.RequestedForEmail)"
displayName: Config Repo
workingDirectory: $(repo)
# Reset branch to its initial commit to enable deterministic snapshot replay
- powershell: |
if (-not (git show-ref refs/heads/${{ parameters.branch }})) {
Write-Host "Migration branch hasn't been initialized"
exit 1
}
git update-ref ORIG_HEAD HEAD # Preserve original HEAD
git symbolic-ref HEAD refs/heads/${{ parameters.branch }} # Switch to target branch
$first = git rev-list --max-parents=0 HEAD # Locate initial commit
git update-ref HEAD "$first" # Reset branch to initial commit
displayName: Reset Branch
workingDirectory: $(repo)
# Iterate over each platform for snapshot processing
- ${{ each platform in parameters.platforms }}:
# Check if snapshot index exists for this platform; sets variable for conditional extraction
- task: AzureCLI@2
displayName: "${{ platform.name }}"
inputs:
azureSubscription: $(migrate.subscription)
scriptType: 'ps'
scriptLocation: 'inlineScript'
inlineScript: |
$exists = az storage blob exists --account-name $(migrate.storage) --auth-mode login --container-name $(migrate.container) --name ${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/snapshots/index.txt
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
if (($exists | ConvertFrom-Json).exists) {
Write-Host "Downloading snapshot index (extract required)"
Write-Host "##vso[task.setvariable variable=extract]$true"
New-Item -Path $(source)\${{ platform.version }}-${{ platform.charset }}\snapshots -Type Directory | Out-Null
az storage blob download --account-name $(migrate.storage) --auth-mode login --container-name $(migrate.container) --file $(source)\${{ platform.version }}-${{ platform.charset }}\snapshots\index.txt --name ${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/snapshots/index.txt
} else {
Write-Host "Snapshot index doesn't exist (extract not required)"
Write-Host "##vso[task.setvariable variable=extract]$false"
}
# If any snapshots exists, download binaries for the platform version
- task: AzureCLI@2
displayName: "Download Binaries"
condition: and(succeeded(), eq(variables.extract, true))
inputs:
azureSubscription: $(migrate.subscription)
scriptType: 'ps'
scriptLocation: 'inlineScript'
inlineScript: |
az storage copy -s "https://$(migrate.storage).blob.core.windows.net/$(migrate.container)/${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/*" -d "$(source)\${{ platform.version }}-${{ platform.charset }}" --auth-mode login --recursive --exclude-path snapshots
# If any snapshots exists, download JadeGit binaries corresponding to platform version
- task: UniversalPackages@0
displayName: "Download JadeGit"
condition: and(succeeded(), eq(variables.extract, true))
inputs:
command: download
vstsFeed: '$(jade.feed)'
vstsFeedPackage: 'jadegit-dev-${{ platform.version }}-${{ platform.charset }}'
vstsPackageVersion: '$(jadegit.version)'
downloadDirectory: $(Agent.TempDirectory)
# Sequentially extract snapshots for this platform, preserving historical commit alignment
- task: AzureCLI@2
displayName: "Extract Snapshots"
condition: and(succeeded(), eq(variables.extract, true))
inputs:
azureSubscription: $(migrate.subscription)
scriptType: 'ps'
scriptLocation: 'inlineScript'
inlineScript: |
$source = "$(source)\${{ platform.version }}-${{ platform.charset }}"
$env:Path = "$source\bin;$env:Path"
Out-File -FilePath $(Agent.TempDirectory)\jade.ini # Temporary config
$last=0
[array]$lines = Get-Content "$source\snapshots\index.txt" # Read snapshot index
foreach ($line in $lines) {
$index++
if (-not $line) { continue }
Write-Host "Downloading snapshot for $line"
if ($last) {
Get-ChildItem $source\snapshots\$last | Remove-Item -Recurse -Force # Remove previous snapshot folder
}
# Download snapshot for this release
az storage copy -s "https://$(migrate.storage).blob.core.windows.net/$(migrate.container)/${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/snapshots/$index/*" -d "$source\snapshots\$index" --auth-mode login --recursive
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# Determine if an original commit exists to preserve entity IDs
$original = git rev-list --grep="$line" ORIG_HEAD
if ($original) {
$original_message = "original: $original"
$original_commit = @("--original-commit", $original)
} else {
$original_message = "first time"
$original_commit = @()
}
# Extract snapshot using JadeGit
$(Agent.TempDirectory)\jadegit -p $source\snapshots\$index -i $(Agent.TempDirectory)\jade.ini extract --commit-message "$line" $original_commit
$last=$index
}
# Prepare staging area for registry initialization and image creation
New-Item -Path $(Build.StagingDirectory)\image -Type Directory -Force | Out-Null
Get-ChildItem $(Build.StagingDirectory)\image | Remove-Item -Recurse -Force
Get-ChildItem $source -Directory -Exclude snapshots | Move-Item -Destination $(Build.StagingDirectory)\image
Move-Item -Path $source\snapshots\$last -Destination $(Build.StagingDirectory)\image\system
Write-Host "##vso[task.setvariable variable=image_exists]$true"
workingDirectory: $(repo)
# Initialize registry in the last code-only snapshot for this platform, forming the empty database image to be published
- powershell: |
$env:Path="$(Build.StagingDirectory)\image\bin;$env:Path"
New-Item -Path $(Build.StagingDirectory)\registry -Type Directory -Force | Out-Null
$(Agent.TempDirectory)\jadegit -p $(Build.StagingDirectory)\image\system -i $(Agent.TempDirectory)\jade.ini registry init --name ${{ parameters.database }} --repo $(repo) --file $(Build.StagingDirectory)\registry\init.jgr
$(Agent.TempDirectory)\jadegit -p $(Build.StagingDirectory)\image\system -i $(Agent.TempDirectory)\jade.ini registry load $(Build.StagingDirectory)\registry\init.jgr --force
$(Agent.TempDirectory)\jadegit -p $(Build.StagingDirectory)\image\system -i $(Agent.TempDirectory)\jade.ini registry show
displayName: "Initialize Registry"
workingDirectory: $(repo)
condition: and(succeeded(), eq(variables.extract, true))
# Apply platform upgrade if required to align the image with the target platform version
- template: ..\images\upgrade.yml
parameters:
path: $(Build.StagingDirectory)\image
database: ${{ parameters.database }}
version: ${{ platform.version }}
charset: ${{ platform.charset }}
condition: and(succeeded(), eq(variables.image_exists, true))
# Publish empty database image
- template: ..\images\publish.yml
parameters:
path: $(Build.StagingDirectory)\image
config: empty
database: ${{ parameters.database }}
version: ${{ platform.version }}
charset: ${{ platform.charset }}
condition: and(succeeded(), eq(variables.image_exists, true))
# Push reconstructed branch to remote repository
- powershell: |
git config push.autoSetupRemote true
git push --force
displayName: Push Repo
workingDirectory: $(repo)
# Publish registry artifact
- publish: $(Build.StagingDirectory)\registry
artifact: '${{ parameters.database }}_registry'
displayName: Publish Registry
# Prepare and publish development images based on the empty database image, with JadeGit preinstalled for developer workspaces
- ${{ each platform in parameters.platforms }}:
- template: ..\images\dev.yml
parameters:
database: ${{ parameters.database }}
dependsOn:
- Migrate
displayName: "Dev - ${{ platform.name }}"
version: ${{ platform.version }}
charset: ${{ platform.charset }}