> For the complete documentation index, see [llms.txt](https://jadelab.gitbook.io/jadegit/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jadelab.gitbook.io/jadegit/0.18.0/admin/database-images/example.md).

# Example

The following example demonstrates how database images can be built using Azure DevOps pipelines. The pipeline and its templates are part of a broader admin repository, with each folder reflecting a separation of concerns.

```
ci/
  ...
images/
  pipeline.yml
  fresh.yml
  empty.yml
  dev.yml
  upgrade.yml
  download.yml
  publish.yml
migration/
  ...
snapshots/
  download.yml
  ...
```

## Pipeline

The `pipeline.yml` orchestrates image creation in three phases — Fresh, Empty, and Dev — each depending on the previous. The first phase runs a separate stage per platform, allowing individual platforms to be skipped when their fresh images are already up to date, while still being covered by subsequent phases. The Empty and Dev phases each run as a single stage, where jobs are created based on the combination of platform and database parameters, which can be changed as needed.

A `preview` parameter is also available, which when enabled causes the `Jade.Admin.Preview` variable group to be included, adjusting variables to publish images to a preview catalog, use more recent artifact versions by changing the feed used, and continue on error where possible to still publish images even if unstable.

```yaml
trigger: none

parameters:
- name: source
  type: string
  displayName: Source Environment
  default: Production

- name: reference
  type: string
  displayName: Snapshot Reference
  default: latest

- name: databases
  type: object
  displayName: Databases
  default:
  - demo
  - finance
  - inventory

- name: platforms
  type: object
  displayName: Jade Platforms
  default:
  - name: Jade 2022 SP4 (ANSI)
    version: 22.0.05
    charset: ansi
  - name: Jade 2025 R1 (ANSI)
    version: 25.0.01
    charset: ansi

- name: preview
  type: boolean
  displayName: Preview
  default: false

variables:
- group: Jade.Admin
- group: Jade.Admin.Snapshot
- ${{ if eq(parameters.preview, true) }}:
  - group: Jade.Admin.Preview

stages:
- ${{ each platform in parameters.platforms }}:
  - stage: "fresh_${{ replace(platform.version, '.', '_') }}_${{ platform.charset }}"
    dependsOn: []
    displayName: "${{ platform.name }}"
    jobs:
    - template: fresh.yml
      parameters:
        version: ${{ platform.version }}
        charset: ${{ platform.charset }}
        preview: ${{ parameters.preview }}

- stage: Empty
  dependsOn:
    - ${{ each platform in parameters.platforms }}:
        - "fresh_${{ replace(platform.version, '.', '_') }}_${{ platform.charset }}"
  jobs:
  - ${{ each database in parameters.databases }}:
    - template: empty.yml
      parameters:
        snapshot:
          environment: ${{ lower(parameters.source) }}
          database: ${{ lower(database) }}
          reference: ${{ lower(parameters.reference) }}
          storage: $(snapshot.scrambled.storage)
          subscription: $(snapshot.scrambled.reader)
        platforms: ${{ parameters.platforms }}
        preview: ${{ parameters.preview }}

- stage: Dev
  dependsOn:
    - Empty
  jobs:
  - ${{ each database in parameters.databases }}:
    - ${{ each platform in parameters.platforms }}:
      - template: dev.yml
        parameters:
          database: ${{ lower(database) }}
          displayName: "${{ database }} - ${{ platform.name }}"
          version: ${{ platform.version }}
          charset: ${{ platform.charset }}
          preview: ${{ parameters.preview }}
```

## Storage

The `publish.yml` and `download.yml` templates are covered here as they are reused across multiple stages. Each stage that creates or consumes an image uses these templates to interact with the catalog storage consistently.

### Publish

The `publish.yml` template publishes a database image to the catalog in Azure Blob Storage, first cleaning the image to remove anything not required, such as logs and temporary files, before copying it to the catalog using the path structure described above.

```yaml
parameters:
- name: path
  type: string

- name: database
  type: string
  
- name: type
  type: string

- name: version
  type: string

- name: charset
  type: string

- name: condition
  type: string
  default: succeeded()

steps:
- task: DeleteFiles@1
  displayName: "Clean Image"
  condition: ${{ parameters.condition }}
  inputs:
    sourceFolder: ${{ parameters.path }}
    contents: |
      bin/**/!(*.eng|*.dll|*.exe|*.bin)
      i686-msoft-win32-ansi/**/!(*.eng|*.dll|*.exe)
      system/**/!(*.dat|db*.log)
      system/journals/archive/*
      system/journals/archive
      x64-msoft-win64-ansi/**/!(*.eng|*.dll|*.exe)
      {CrashLogs,ProcessDumps,logs,temp}/**/*
      {CrashLogs,ProcessDumps,logs,temp}
      !(*.ini|*.ps1)
    removeDotFiles: true

- task: AzureFileCopy@6
  displayName: "Publish Image (${{ parameters.type }}/${{ parameters.database }})"
  condition: ${{ parameters.condition }}
  inputs:
    sourcePath: ${{ parameters.path }}\*
    destination: 'AzureBlob'
    blobPrefix: ${{ parameters.version }}-${{ parameters.charset }}/${{ parameters.type }}/${{ parameters.database }}
    containerName: $(images.container)
    storage: $(images.storage)
    azureSubscription: $(images.subscription)
    additionalArgumentsForBlobCopy: --recursive --put-md5
    cleanTargetBeforeCopy: true
```

### Download

The `download.yml` template downloads a database image from the catalog, copying it from Azure Blob Storage to a local path. It also sets an `image_exists` variable to indicate whether the image was found, which is used by subsequent steps to conditionally skip processing when no image is available to update.

```yaml
parameters:
- name: path
  type: string

- name: database
  type: string
  
- name: type
  type: string

- name: version
  type: string

- name: charset
  type: string

- name: condition
  type: string
  default: succeeded()

steps:
- task: AzureCLI@2
  condition: ${{ parameters.condition }}
  displayName: 'Download Image (${{ parameters.version }}-${{ parameters.charset }}/${{ parameters.type }}/${{ parameters.database }})'
  inputs:
    azureSubscription: $(images.subscription)
    scriptType: 'ps'
    scriptLocation: 'inlineScript'
    inlineScript: |
      az config set extension.use_dynamic_install=yes_without_prompt
      az storage copy -s "https://$(images.storage).blob.core.windows.net/$(images.container)/${{ parameters.version }}-${{ parameters.charset }}/${{ parameters.type }}/${{ parameters.database }}/*" -d "${{ parameters.path }}" --auth-mode login --recursive
      Write-Host "##vso[task.setvariable variable=image_exists]$(Test-Path ${{ parameters.path }}/*)"
```
