Skip to content

Latest commit

 

History

193 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Yamlizr - Azure DevOps Designer-to-YAML Pipeline Conversion Tool

CI Coverage Status SonarCloud Coverage Nuget

yamlizr converts Azure DevOps Classic Designer Build/Release Definitions, and any Task Groups they reference, en-masse into their YAML Pipeline or GitHub Actions equivalent. It runs as a container, or as a .NET Global Tool.

Disclaimer: Do not consider any of the YAML generated by this tool to be 'production ready'. Do your own testing/research and post any issues and/or make a PR!

If you find this tool of use then please give it a thumbs-up by giving this repository a ⭐ ... 😉

Quick Start

You need Docker and an Azure DevOps Personal Access Token, nothing else.

docker run --pull always --rm \
  --user "$(id -u):$(id -g)" \
  -e CasCap__AzureDevOpsOptions__PAT="<your token here>" \
  -v "$(pwd)/out:/data" \
  ghcr.io/f2calv/yamlizr generate \
    -org https://dev.azure.com/myorg \
    -proj MyProject \
    -out /data \
    --create-directory

The same thing in PowerShell. Docker Desktop maps file ownership for you, so no --user is needed;

docker run --pull always --rm `
  -e CasCap__AzureDevOpsOptions__PAT="<your token here>" `
  -v "${PWD}/out:/data" `
  ghcr.io/f2calv/yamlizr generate `
    -org https://dev.azure.com/myorg `
    -proj MyProject `
    -out /data `
    --create-directory

The generated YAML appears in ./out/MyProject/, see Output Layout.

Installation

Container

The container is the easiest way to run yamlizr because it needs no .NET installation. A multi-architecture image supporting linux/amd64, linux/arm64 and linux/arm/v7 is published to GitHub Container Registry alongside the NuGet package.

docker pull ghcr.io/f2calv/yamlizr
Tag Meaning
latest The most recent release, moves with every release.
4.0.0 An exact release. Published image versions are immutable and are never overwritten, so pin this in automation.

Notes on running the image;

  • Staying current. --pull always re-checks the registry on every run, so a floating tag such as latest cannot go stale. Omit it once you have pinned an exact version, which never changes.
  • File ownership. The image runs as a non-root user (uid 1654). On Linux pass --user "$(id -u):$(id -g)" so the generated files belong to you. This is unnecessary on Docker Desktop for Windows or macOS.
  • --create-directory is effectively required. yamlizr always writes into a sub-folder named after the project, and creating that folder raises an interactive prompt which cannot be answered without a TTY. Either pass --create-directory, or run with -it.
  • Passing the token. Prefer --env-file, or a bare -e CasCap__AzureDevOpsOptions__PAT which forwards the value from your shell. An inline -e NAME=value is visible in docker inspect and in your shell history.
  • Never bake a token into an image, every file in an image is public.

.NET Global Tool

If you already have the .NET SDK then the global tool is the lighter option, and it writes directly to your filesystem with no volume or user mapping to think about.

  • Download and install one of;

  • From a command line shell install (or update) the tool;

    dotnet tool update --global yamlizr
  • Generate YAML into the c:/temp/myoutputfolder output folder;

    yamlizr generate -pat <your token here> -org https://dev.azure.com/myorg -proj MyProject -out c:/temp/myoutputfolder

Every example below works with either form. Where a command reads yamlizr generate ... the container equivalent is docker run --rm ... ghcr.io/f2calv/yamlizr generate ....

Personal Access Token

Create a Personal Access Token (PAT) with the following scopes/permissions;

Scope Permission
Build Read
Deployment Groups Read & Manage
Release Read
Task Groups Read
Variable Groups Read

The token is never persisted by yamlizr. It can also be an access token issued to a pipeline's build service identity, see Running Inside a Pipeline.

Usage

For context-sensitive help execute;

yamlizr --help
yamlizr generate --help

Required values, each of which may instead come from configuration;

  • -pat|--token Personal Access Token, or a pipeline access token.
  • -org|--organisation Azure DevOps organisation Uri, e.g. https://dev.azure.com/myorg.
  • -proj|--project Azure DevOps project name.

Optional arguments;

  • -out|--outputpath path to the YAML output folder, defaults to the current directory.
  • --filter <some string here> only convert definitions whose name contains this text. It is a case-insensitive contains match, not a wildcard pattern, so pass yamlizr.test. rather than yamlizr.test.*.
  • --phasetype <phase type here> filter deployment jobs by Deploy Phase Type the default is AgentBasedDeployment DeployPhaseTypes(tested), other (un-tested) options are RunOnServer, MachineGroupBasedDeployment & DeploymentGates.

Optional switches;

  • --inline merge the tasks from task groups into the steps of the calling job instead of creating additional template files.
  • --githubactions generate GitHub Actions workflows via AzurePipelinesToGitHubActionsConverter.
  • --create-directory create the output folder without prompting, for unattended runs.
  • --parallelism generate pipelines in parallel.

Examples

Convert every Build and Release Definition in a project, as a container;

docker run --pull always --rm \
  --user "$(id -u):$(id -g)" \
  --env-file ./yamlizr.env \
  -v "$(pwd)/out:/data" \
  ghcr.io/f2calv/yamlizr generate -org https://dev.azure.com/myorg -proj MyProject -out /data --create-directory

Convert only the definitions whose name contains wibble, and emit GitHub Actions workflows as well, as a global tool;

yamlizr generate `
  -pat <your token here> `
  -org https://dev.azure.com/myorg `
  -proj MyProject `
  -out c:/temp/myoutputfolder `
  --filter wibble `
  --githubactions

Output Layout

All generated YAML is written into sub-folders of a project folder, so a run with -out c:/temp/myoutputfolder creates;

  • c:/temp/myoutputfolder/<your AzDO project>/AzureDevOpsBuilds/*.yml
  • c:/temp/myoutputfolder/<your AzDO project>/AzureDevOpsReleases/*.yml
  • c:/temp/myoutputfolder/<your AzDO project>/AzureDevOpsTaskGroups/*.yml
  • c:/temp/myoutputfolder/<your AzDO project>/GitHubBuilds/*.yml (with --githubactions)
  • c:/temp/myoutputfolder/<your AzDO project>/GitHubReleases/*.yml (with --githubactions)

Each run finishes with a summary of anything it could not convert, see Conversion Coverage.

Configuration

The token, organisation and project can also come from configuration, so an unattended run does not have to put a credential on a process command line. A command line option always wins over a configured value.

Sources are read in the following order, each overriding the last;

  1. appsettings.json alongside the tool
  2. appsettings.json in the current working directory
  3. .NET User Secrets
  4. environment variables
  5. the predefined Azure Pipelines variables, see Running Inside a Pipeline
{
  "CasCap": {
    "AzureDevOpsOptions": {
      "PAT": null,
      "OrganisationUri": "https://dev.azure.com/myorg",
      "Project": "MyProject"
    }
  }
}

The equivalent environment variables use the standard double-underscore separator, and are what the container expects;

$env:CasCap__AzureDevOpsOptions__PAT = '<your token here>'
$env:CasCap__AzureDevOpsOptions__OrganisationUri = 'https://dev.azure.com/myorg'
$env:CasCap__AzureDevOpsOptions__Project = 'MyProject'
yamlizr generate -out c:/temp/myoutputfolder

Never commit a token. Use User Secrets locally and a secret-backed environment variable in CI.

Running Inside a Pipeline

--token also accepts an access token issued to a pipeline's build service identity, so yamlizr can run as a step in an Azure Pipeline without a Personal Access Token. Grant the build service the same read scopes listed above and pass $(System.AccessToken);

- script: |
    dotnet tool update --global yamlizr
    yamlizr generate --token $(System.AccessToken) -org $(System.CollectionUri) -proj $(System.TeamProject) -out $(Build.ArtifactStagingDirectory) --create-directory
  displayName: yamlizr

Alternatively supply nothing and let yamlizr fall back to the predefined pipeline variables SYSTEM_ACCESSTOKEN, SYSTEM_COLLECTIONURI and SYSTEM_TEAMPROJECT. The latter two are exposed to every step automatically, but SYSTEM_ACCESSTOKEN is secret and reaches a step only when you map it explicitly;

- script: |
    dotnet tool update --global yamlizr
    yamlizr generate -out $(Build.ArtifactStagingDirectory) --create-directory
  displayName: yamlizr
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)

The container needs no .NET on the agent. Nothing from the agent reaches it unless you forward it, so the predefined variables above are not available inside the container and the organisation and project have to be passed as arguments;

- script: |
    docker run --pull always --rm \
      --user "`id -u`:`id -g`" \
      -e CasCap__AzureDevOpsOptions__PAT \
      -v "$(Build.ArtifactStagingDirectory):/data" \
      ghcr.io/f2calv/yamlizr generate \
        -org $(System.CollectionUri) \
        -proj $(System.TeamProject) \
        -out /data \
        --create-directory
  displayName: yamlizr
  env:
    CasCap__AzureDevOpsOptions__PAT: $(System.AccessToken)

Two details worth keeping;

  • -e NAME with no value forwards the variable from the step environment, keeping the token out of the command line and out of docker inspect.
  • The user mapping uses backticks because Azure Pipelines resolves its own $( ) macros before bash sees the script. Without it the generated files belong to uid 1654 and the agent cannot clean them up.

How It Works

This tool was created when there was no means of exporting a designer/classic build/release definition to YAML. As of November 2020 there is a new Export to YAML feature which allows you to export a Build pipeline to YAML with a single click. This official function covers more edge cases than this CLI for Build pipelines. Where this CLI still has benefits is that it also converts Release definitions to YAML, which the official tool does not. It also allows the conversion of every single Build/Release definition en-masse, so much less clicking! Then you can then cut/copy/paste/manipulate the generated YAML steps as required to fit into a build and/or deployment pipeline unique to your own requirements.

The tool itself uses the Azure DevOps .NET Client Libraries to pre-cache relevant data from your Azure DevOps organisation/account. This data includes build/release definitions, task groups, tasks/extensions data and variable groups. This Azure DevOps data is converted into Azure DevOps Pipeline objects (stages/jobs/steps/variables) which are then persisted to YAML using the YamlDotNet library.

This is not a delicate tool to create perfectly constructed YAML pipelines. Instead consider it to be a blunt instrument which will spawn as many YAML files as possible and from this YAML you can pick/choose and copy/paste the relevant stages/jobs/steps/variables into your own preferred YAML CI/CD deployment architecture.

With --githubactions the same Azure DevOps Pipeline objects are additionally passed into the AzurePipelinesToGitHubActionsConverter library (by @samsmithnz) and exported as GitHub Actions compliant workflows.

Conversion Coverage

Every run finishes with a summary naming each construct it could not convert and the definition it came from, so nothing is dropped without a trace. Anything marked ❌ below is absent from the generated YAML and must be re-created by hand. Progress on closing these gaps is tracked in issue #182.

✅ converted    ⚠️ partially converted    ❌ not converted

Build Definitions

Classic construct Notes
Agent phases Each phase becomes a job.
Job dependsOn Mapped from the phase's declared dependencies, including fan-in, so phases meant to run in parallel still do.
Job condition, timeout and cancel timeout
Agent queue Becomes the pipeline pool.
Continuous integration trigger Including branch and path include/exclude filters, and batching.
Pull request, scheduled and build completion triggers Reported in the run summary.
Server and deployment group phases Only agent phases are converted.
A definition with exactly one job ⚠️ Flattened to a bare step list, dropping a non-default job condition, see issue #376.

Release Definitions

Classic construct Notes
Environments Each environment becomes a stage named after it.
Deploy phases matching --phasetype Defaults to AgentBasedDeployment.
Job dependsOn within a stage Sequential, in rank order.
Job condition, timeout and cancel timeout
Stage dependsOn Generated stages run concurrently rather than in the classic environment order.
Release artifacts A generated release pipeline therefore has no inputs.
Pre- and post-deployment approvals and gates Detected and reported, but not converted, see issue #374. A generated release pipeline deploys straight through where the classic definition had a gate.
Deploy phases other than --phasetype Reported per stage.
A definition with exactly one stage ⚠️ Flattened, dropping stage-level variables and variable groups, see issue #376.

Steps, Variables and Task Groups

Classic construct Notes
Steps for installed tasks With condition, continueOnError, timeoutInMinutes and env.
Task groups Emitted as template files, or merged into the calling job with --inline.
Task group parameters Become template parameters.
Variables Pipeline, stage and environment scope.
Variable groups Emitted as a group reference.
GitHub Actions workflows With --githubactions, via AzurePipelinesToGitHubActionsConverter.
Steps referencing an extension not installed in the organisation Reported by display name and task id.
Steps whose task version cannot be parsed Reported by display name and task id.
Combined build plus release multi-stage pipelines Build and release definitions are emitted as separate files.

Project Structure

Project Purpose
CasCap.Api.AzureDevOps Library: Azure DevOps REST access, pipeline models, and the YAML generator
CasCap.DevOpsYamlizrCli The yamlizr global tool: command surface, console presentation, orchestration
CasCap.Api.AzureDevOps.Tests xUnit v3 tests running on Microsoft.Testing.Platform

Core Dependencies

Feedback/Issues

Please post any issues or feedback to GitHub issues.

License

This project is released under The Unlicense. See the LICENSE file for details.

About

Azure DevOps Designer-to-YAML pipeline CLI with GitHub Actions generation.

Topics

Resources

Stars

92 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages