adop-cli 1.4.1
CLI tool to trigger Azure DevOps pipelines
Requires Python: >=3.10

# Azure DevOps Pipeline CLI

A command-line tool to trigger and manage Azure DevOps pipelines with ease.

## Features

- **Trigger Pipelines** - Start builds with a single command
- **Hierarchical Context** - Manage multiple orgs and projects with local config support
- **Smart Init** - Auto-detects Azure DevOps org/project from git remote URL
- **Duplicate Detection** - Prevents triggering duplicate builds
- **Watch Progress** - Real-time build status with progress bar
- **Build Management** - View status, logs, cancel, compare builds
- **Favorites** - Save and reuse common build configurations
- **Shell Completion** - Tab-complete pipeline aliases and branches
- **Fuzzy Matching** - Suggests corrections for typos

## Installation

### Homebrew (macOS/Linux)

```bash
brew tap SAGARSURI/tap
brew install adop-cli
```

### pip

```bash
pip install adop-cli
```

### From Source

```bash
git clone https://github.com/SAGARSURI/adop.git
cd adop
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```

## Setup

### 1. Create an Organization

```bash
ado-pipeline org add work
```

You'll be prompted to enter:
- **Organization** - Your Azure DevOps organization name
- **PAT** - Personal Access Token

**Required PAT Permissions:**
- Build: Read & execute
- Code: Read

### 2. Add a Project

```bash
ado-pipeline project add mobile-app
```

You'll be prompted for the Azure DevOps project name.

### 3. Add Pipelines

After configuring, add pipelines you want to manage:

```bash
# Import pipelines from Azure DevOps (interactive)
ado-pipeline pipeline import

# Or add manually
ado-pipeline pipeline add my-build "My Pipeline Name" --description "Build description"

# Add with parameters
ado-pipeline pipeline add android-build "Android_Build" \
  --param "outputFormat:choice:apk:apk,aab" \
  --param "deploy:boolean:false" \
  --param "releaseNotes:string:"
```

### 4. Enable Shell Completion (Optional)

```bash
# Bash - add to ~/.bashrc
eval "$(_ADO_PIPELINE_COMPLETE=bash_source ado-pipeline)"

# Zsh - add to ~/.zshrc
eval "$(_ADO_PIPELINE_COMPLETE=zsh_source ado-pipeline)"
```

Then restart your terminal or run `source ~/.zshrc` (or `~/.bashrc`).

**What it completes:**

| Context | Example | Completes |
|---------|---------|-----------|
| Commands | `ado-pipeline pl<Tab>` | `plan`, `pipeline` |
| Pipeline aliases | `ado-pipeline apply <Tab>` | Your configured pipelines |
| Branch names | `ado-pipeline apply my-build -b <Tab>` | Git branches |
| Subcommands | `ado-pipeline pipeline <Tab>` | `list`, `add`, `remove`, etc. |

## Quick Start

```bash
# 1. Setup (one-time)
ado-pipeline org add myorg               # Add your Azure DevOps org
ado-pipeline project add myproject       # Add your project
ado-pipeline pipeline import             # Import pipelines

# 2. Daily usage
ado-pipeline pipeline list               # See available pipelines
ado-pipeline plan my-build               # Preview (dry-run)
ado-pipeline apply my-build -y -w        # Trigger + watch
ado-pipeline status                      # Check build status
ado-pipeline open <BUILD_ID>             # Open in browser
```

## Commands

### Trigger Pipelines

| Command | Description |
|---------|-------------|
| `ado-pipeline plan <alias>` | Dry-run - preview what will be triggered |
| `ado-pipeline apply <alias>` | Trigger the pipeline |

#### Options

| Option | Behavior |
|--------|----------|
| *(none)* | Auto-detects current git branch |
| `--branch`, `-b` | Build from specified branch |
| `--pr` | Build from pull request (uses `refs/pull/{n}/merge`) |
| `--skip-duplicate-check`, `-f` | Bypass duplicate build check |
| `--watch`, `--follow`, `-w` | Watch build progress until completion |

> `--branch` and `--pr` cannot be used together.

#### Examples

```bash
# Dry-run (preview only, no API call)
ado-pipeline plan android-dev-build

# Trigger build on current branch
ado-pipeline apply android-dev-build

# Trigger on specific branch
ado-pipeline apply android-dev-build --branch main

# Trigger from PR #123
ado-pipeline apply android-dev-build --pr 123

# With parameters
ado-pipeline apply android-dev-build -p deploy=true -p outputFormat=aab

# Skip confirmation + watch progress
ado-pipeline apply android-dev-build -y -w
```

### Build Management

#### View Build Status

```bash
# Show recent builds
ado-pipeline status

# Show more builds
ado-pipeline status -n 20

# Filter by pipeline
ado-pipeline status -P my-build

# Show only your builds
ado-pipeline status --mine
```

#### View Build Logs

```bash
# Show logs for a build
ado-pipeline logs <BUILD_ID>

# Stream logs in real-time
ado-pipeline logs <BUILD_ID> -f
```

#### Cancel a Build

```bash
ado-pipeline cancel <BUILD_ID>
ado-pipeline cancel <BUILD_ID> -y    # Skip confirmation
```

#### Open Build in Browser

```bash
ado-pipeline open <BUILD_ID>
```

#### Compare Two Builds

```bash
ado-pipeline diff <BUILD_ID_1> <BUILD_ID_2>
```

Shows side-by-side comparison of pipeline, branch, result, duration, and parameters.

### Pipeline Management

#### List Configured Pipelines

```bash
ado-pipeline pipeline list
```

#### List Remote Pipelines

```bash
# List all pipelines from Azure DevOps
ado-pipeline pipeline list --remote
ado-pipeline pipeline list -r            # Short form
```

Shows all pipelines available in your Azure DevOps project with their IDs and folders.

#### Add a Pipeline

```bash
ado-pipeline pipeline add <alias> "<Azure DevOps Pipeline Name>"

# With parameters (format: name:type:default[:choices])
ado-pipeline pipeline add android-dev-build "Build_Android_Dev" \
  --param "outputFormat:choice:apk:apk,aab" \
  --param "deploy:boolean:false"
```

Parameter types: `string`, `boolean`, `choice`

#### Import from Azure DevOps

```bash
# Interactive import with batch selection
ado-pipeline pipeline import

# Import all pipelines without prompting
ado-pipeline pipeline import --all

# Filter by name pattern (glob or regex)
ado-pipeline pipeline import --filter "Android*"
ado-pipeline pipeline import -f "Build_.*_Dev"

# Skip auto-fetching parameters (faster bulk import)
ado-pipeline pipeline import --all --no-params
```

The import command shows a numbered table and lets you select multiple pipelines at once:

```
Found 35 pipeline(s) to import:

   #   Pipeline Name               Suggested Alias
   1   Build_Android_Dev           build-android-dev
   2   Build_Android_Prod          build-android-prod
  ...

Select pipelines (e.g., 1,3,5-10 or 'all' or 'none'): 1-5,10

Importing 6 pipeline(s)...
```

#### Sync Parameters from Azure DevOps

```bash
# Sync single pipeline
ado-pipeline pipeline sync my-build

# Sync all pipelines
ado-pipeline pipeline sync --all

# Interactive batch selection
ado-pipeline pipeline sync
```

Parameters are auto-fetched during import. Use `sync` to refresh if the pipeline's YAML changes.

#### Remove Pipelines

```bash
# Remove single pipeline
ado-pipeline pipeline remove my-build

# Remove all pipelines (with confirmation)
ado-pipeline pipeline remove --all

# Interactive batch selection
ado-pipeline pipeline remove
```

The batch selection supports ranges like `1,3,5-10` or `all`:

### Favorites

Save frequently used build configurations for quick access.

#### Save a Favorite

```bash
# Save with deploy enabled
ado-pipeline fav add quick-build my-build --deploy

# Save with specific branch
ado-pipeline fav add release-build my-prod --branch main --deploy
```

#### List Favorites

```bash
ado-pipeline fav list
```

#### Run a Favorite

```bash
ado-pipeline fav run quick-build
ado-pipeline fav run quick-build -y -w    # Skip confirmation + watch
```

#### Remove a Favorite

```bash
ado-pipeline fav remove quick-build
```

### Configuration

```bash
# Initialize local project config (.ado-pipeline.json)
# Auto-detects org/project from Azure DevOps git remote
ado-pipeline init

# Show current configuration
ado-pipeline config show
```

## Hierarchical Context (Org/Project)

Manage multiple Azure DevOps organizations and projects.

### Organization Management

```bash
# Add organizations
ado-pipeline org add work        # Prompts for Azure DevOps org + PAT
ado-pipeline org add personal

# List organizations (active marked with *)
ado-pipeline org list

# Switch organization
ado-pipeline org select personal

# Remove organization
ado-pipeline org remove old-org
```

### Project Management

```bash
# Add project to active org
ado-pipeline project add mobile-app

# List projects in active org
ado-pipeline project list

# Switch project
ado-pipeline project select web-app

# Remove project
ado-pipeline project remove old-project
```

### Context Commands

```bash
# Show current context and source
ado-pipeline context

# Quick switch (org/project)
ado-pipeline use work/mobile-app
```

### Override Context for Single Command

```bash
# -O (org) and -J (project) flags
ado-pipeline -O personal -J hobby status
ado-pipeline -O work -J mobile apply android-build

# Or use environment variables
ADO_ORG=personal ADO_PROJECT=hobby ado-pipeline status
```

### Local Project Config

Create a `.ado-pipeline.json` in your project directory to auto-select context:

```bash
# In your project directory
ado-pipeline init
```

The `init` command auto-detects your Azure DevOps org and project from the git remote:

- **ADO remote detected**: Automatically uses the matching org/project. If no local config exists, prompts for PAT to create one.
- **Non-ADO remote (GitHub, GitLab, etc.)**: Warns you and asks for confirmation before proceeding with active context.
- **No git remote**: Falls back to current active context.

Or manually create the file:

```json
{
  "org": "work",
  "project": "mobile-app"
}
```

Now commands auto-detect context when you're in that directory:

```bash
cd ~/projects/mobile-app
ado-pipeline apply android    # Uses work/mobile-app automatically
```

**Context Resolution Priority:**
1. CLI flags (`-O`, `-J`)
2. Environment variables (`ADO_ORG`, `ADO_PROJECT`)
3. Local config (`.ado-pipeline.json` in current or parent dirs)
4. Global active context

## Parameters

Pass parameters with `-p name=value`:

```bash
ado-pipeline apply android-dev-build -p deploy=true -p outputFormat=aab
```

- **Boolean values:** `true/yes/1` or `false/no/0`
- **Refresh parameters:** `ado-pipeline pipeline sync <alias>`

## Configuration Files

```
~/.azure-pipeline-cli/
├── active_context              # Current org/project (e.g., "work/mobile-app")
└── orgs/
    └── <org-name>/
        ├── config.json         # PAT and Azure DevOps organization
        └── projects/
            └── <project-name>/
                ├── config.json      # Azure DevOps project settings
                ├── pipelines.json   # Pipeline definitions
                └── favorites.json   # Saved favorite configurations
```

**Local project config** (optional):
```
~/projects/my-app/
└── .ado-pipeline.json          # Auto-selects org/project context
```

All config files have `0600` permissions (owner read/write only) since they may contain sensitive data.

## Environment Variables

| Variable | Description |
|----------|-------------|
| `ADO_ORG` | Override organization for the command |
| `ADO_PROJECT` | Override project for the command |
| `ADO_BANNER` | Control ASCII banner display: `0` to disable, `1` to force enable |
| `NO_COLOR` | Standard variable to disable colors (also hides banner) |

## Troubleshooting

### "Pipeline not found"

The CLI suggests similar names if you make a typo:
```
Error: Pipeline 'my-bild' not found. Did you mean: my-build?
```

Run `ado-pipeline pipeline list` to see all available aliases.

### "No pipelines configured"

Add pipelines first:
```bash
ado-pipeline pipeline import   # Import from Azure DevOps
# or
ado-pipeline pipeline add <alias> "<pipeline-name>"
```

### "PAT not configured" / "No context set"

Set up an organization and project:
```bash
ado-pipeline org add work      # Creates org, prompts for PAT
ado-pipeline project add myapp # Creates project in active org
```

### "Branch not found"

The branch must exist on the remote. Push it first:
```bash
git push -u origin <branch>
```

### "Duplicate build detected"

A build with the same branch, commit, and parameters is already running. Options:
- Wait for the existing build to complete
- Use `--skip-duplicate-check` (`-f`) to trigger anyway: `ado-pipeline apply my-build -f`
- Cancel the existing build: `ado-pipeline cancel <BUILD_ID>`

### Shell completion not working

Make sure you've added the eval command to your shell config and restarted your terminal.

### Build stuck or need to cancel

```bash
ado-pipeline cancel <BUILD_ID> -y
```

## Development

### Setup

```bash
git clone https://github.com/SAGARSURI/adop.git
cd adop
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

### Running Tests

```bash
pytest tests/ -v
```

### Project Structure

```
src/ado_pipeline/
├── __init__.py      # Version
├── api.py           # Azure DevOps API client
├── banner.py        # ASCII art banner
├── cli.py           # CLI entry point (re-exports from cli/)
├── cli/             # CLI command modules
│   ├── __init__.py  # Assembles all commands
│   ├── main.py      # Main group and global options
│   ├── helpers.py   # Shared helper functions
│   ├── org_cmd.py   # Organization commands
│   ├── project_cmd.py   # Project commands
│   ├── context_cmd.py   # Context commands
│   ├── build_cmd.py     # Build commands (plan, apply, status, etc.)
│   ├── pipeline_cmd.py  # Pipeline management
│   ├── fav_cmd.py       # Favorites commands
│   ├── config_cmd.py    # Config commands
│   └── completions.py   # Shell completion helpers
├── config.py        # Configuration management
├── context.py       # Hierarchical org/project context
├── duplicate.py     # Duplicate build detection
├── favorites.py     # Favorites system
├── git.py           # Git provider detection and ADO URL parsing
├── pipelines.py     # Pipeline definitions
└── plan.py          # Execution plan generation
```

## CI/CD

### Automated Testing

Tests run automatically on:
- Push to `main`
- Pull requests to `main`

Tests run against Python 3.10, 3.11, and 3.12.

[![CI](https://github.com/SAGARSURI/adop/actions/workflows/ci.yml/badge.svg)](https://github.com/SAGARSURI/adop/actions/workflows/ci.yml)

### Releasing

Releases are automated via GitHub Actions:

1. Update version in both files:
   - `pyproject.toml`
   - `src/ado_pipeline/__init__.py`
2. Commit and tag:
   ```bash
   git add pyproject.toml src/ado_pipeline/__init__.py
   git commit -m "chore: bump version to X.Y.Z"
   git tag vX.Y.Z
   git push origin main --tags
   ```
3. The release workflow automatically:
   - Runs tests
   - Publishes to PyPI
   - Updates Homebrew tap

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

MIT License - see [LICENSE](LICENSE) for details.