---
name: azure-pipeline-ansible
description: Extends azure-devops-pipeline for Ansible playbook runs. Handles syntax check, galaxy install, vault passwords, SSH key injection, check mode on nonprod, and dynamic AWS EC2 inventory. Always load azure-devops-pipeline first.
---

## What I add

Type-specific steps for Ansible pipelines. Merge these into the skeleton from `azure-devops-pipeline`.

## Additional required inputs — ask the user

1. **Playbook path** — e.g. `playbooks/site.yml`
2. **Inventory source** — `static` | `dynamic-aws-ec2`
3. **Ansible Vault in use** — `yes` | `no`
4. **ADO secret variable name for vault password** — if vault in use, e.g. `ANSIBLE_VAULT_PASSWORD`
5. **ADO secret variable name for SSH private key** — e.g. `ANSIBLE_SSH_KEY`
6. **Ansible version to pin** — e.g. `9.2.0`
7. **Run --check mode on nonprod before real apply** — `yes` (default) | `no`

## Lint stage steps

```yaml
- script: |
    pip install "ansible==$(ANSIBLE_VERSION)" ansible-lint
    ansible-lint <playbook-path> --profile production
  displayName: "Lint — ansible-lint"
  env:
    ANSIBLE_VERSION: <ansible-version>
```

## Security scan stage steps

```yaml
- script: |
    pip install "ansible==$(ANSIBLE_VERSION)" ansible-lint
    ansible-lint <playbook-path> --profile security \
      --sarif-file ansible-lint-security.sarif || true
    ansible-galaxy install -r requirements.yml --force
  displayName: "Security scan — ansible-lint security profile"
  env:
    ANSIBLE_VERSION: <ansible-version>
- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: ansible-lint-security.sarif
    artifactName: security-scan
  displayName: "Publish scan results"
```

## Build stage steps

```yaml
- script: |
    pip install "ansible==$(ANSIBLE_VERSION)"
    [ -f requirements.yml ] && ansible-galaxy install -r requirements.yml || true
    ansible-playbook <playbook-path> --syntax-check -i <inventory-file>
  displayName: "Validate — syntax check and galaxy install"
  env:
    ANSIBLE_VERSION: <ansible-version>
```

Note: for dynamic-aws-ec2 inventory, replace `-i <inventory-file>` with `-i aws_ec2.yml` and ensure `aws_ec2.yml` exists in the repo with the `amazon.aws.aws_ec2` plugin configured.

## Deploy stage steps

### Step order — always emit in this order

1. Write SSH key to temp file
2. Write vault password to temp file (if vault in use)
3. Check mode run (nonprod only, if enabled)
4. Real playbook run
5. Clean up SSH key (condition: always)
6. Clean up vault password (condition: always)

### SSH key injection (always include)

```yaml
- script: |
    echo "$(ANSIBLE_SSH_KEY)" > /tmp/ansible_ssh_key
    chmod 600 /tmp/ansible_ssh_key
  displayName: "Inject SSH key"
  env:
    ANSIBLE_SSH_KEY: $(ANSIBLE_SSH_KEY)
```

### Vault password file (include only if vault in use)

```yaml
- script: |
    echo "$(ANSIBLE_VAULT_PASSWORD)" > /tmp/vault_pass
    chmod 600 /tmp/vault_pass
  displayName: "Write vault password file"
  env:
    ANSIBLE_VAULT_PASSWORD: $(ANSIBLE_VAULT_PASSWORD)
```

### Check mode run (nonprod only, if enabled)

```yaml
- script: |
    VAULT_ARGS=""
    [ -f /tmp/vault_pass ] && VAULT_ARGS="--vault-password-file /tmp/vault_pass"
    ansible-playbook <playbook-path> \
      -i <inventory> \
      --check \
      --diff \
      --private-key /tmp/ansible_ssh_key \
      $VAULT_ARGS
  displayName: "Dry run — check mode"
```

### Real run

```yaml
- script: |
    VAULT_ARGS=""
    [ -f /tmp/vault_pass ] && VAULT_ARGS="--vault-password-file /tmp/vault_pass"
    ansible-playbook <playbook-path> \
      -i <inventory> \
      --diff \
      --private-key /tmp/ansible_ssh_key \
      $VAULT_ARGS
  displayName: "Apply playbook"
```

### Cleanup (always at end of deploy steps — condition: always())

```yaml
- script: rm -f /tmp/ansible_ssh_key
  displayName: "Clean up SSH key"
  condition: always()

- script: rm -f /tmp/vault_pass
  displayName: "Clean up vault password file"
  condition: always()
```

## Hard rules for Ansible

- Always pin Ansible version with quoted pip specifier `"ansible==$(ANSIBLE_VERSION)"` — never use `latest`, unquoted `==` may fail in some shells
- Always clean up SSH key and vault password files with `condition: always()` — they must be removed even if the playbook fails
- Always include `--diff` on real runs so changes are visible in pipeline logs
- SSH key file permissions must be `600` — Ansible refuses keys with broader permissions
- Use shell variable expansion (`VAULT_ARGS=""`) rather than subshell substitution in the step script to avoid bash syntax issues in ADO agents
- For dynamic inventory, AWS credentials come from the OIDC service connection environment — same pattern as Lambda
- `requirements.yml` must exist in the repo if galaxy install step is included; if uncertain, wrap with `[ -f requirements.yml ] && ansible-galaxy install -r requirements.yml || true`
