Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- .woodpecker.yaml: image paths -> library/autojanet-{agent,dispatcher}
- .woodpecker.yaml: secret names RS_HARBOR_USER / RS_HARBOR_PASS (global)
- container/Dockerfile: restore COPY skills/, skills/ populated from opencode config
- skills/: 84 opencode skills bundled into image
- k8s/manifests: update image refs to library/
180 lines
5.6 KiB
Markdown
180 lines
5.6 KiB
Markdown
---
|
|
name: azure-devops-pipeline
|
|
description: Generates Azure DevOps pipeline YAML using EKS-Pool with nonprod auto-deploy and prod manual approval gate. Always load this skill first, then load the type-specific skill before generating any YAML.
|
|
---
|
|
|
|
## What I do
|
|
|
|
Guide the generation of a complete `azure-pipelines.yml` file for a self-hosted EKS-Pool Azure DevOps agent pool. I define all shared standards. You MUST also load the appropriate type skill before generating YAML:
|
|
|
|
- Lambda deployments → load `azure-pipeline-lambda`
|
|
- Ansible playbooks → load `azure-pipeline-ansible`
|
|
- Docker builds → load `azure-pipeline-docker`
|
|
|
|
## IMPORTANT — do not generate YAML without loading a type skill
|
|
|
|
STOP. Before generating any pipeline YAML, you MUST load the type skill that matches the requested pipeline type:
|
|
- `azure-pipeline-lambda` for Lambda
|
|
- `azure-pipeline-ansible` for Ansible
|
|
- `azure-pipeline-docker` for Docker
|
|
|
|
Generate nothing until that skill is loaded.
|
|
|
|
## Required inputs — ask the user for these before generating
|
|
|
|
1. **Service/repo name** — used in display names and tags
|
|
2. **Pipeline type** — `lambda` | `ansible` | `docker`
|
|
3. **Target tier** — `nonprod` | `prod`
|
|
4. **Trigger branch** — branch that triggers auto-deploy (default: `main`)
|
|
5. **Secret sources** — which are in use: `ADO variable groups` | `AWS SSM/Secrets Manager` | `Vault/OpenBao` (can be multiple)
|
|
6. **ADO variable group name(s)** — if ADO variable groups selected
|
|
|
|
## Pipeline skeleton — always use this structure
|
|
|
|
```yaml
|
|
trigger:
|
|
branches:
|
|
include:
|
|
- <trigger-branch>
|
|
|
|
pool: EKS-Pool
|
|
|
|
stages:
|
|
- stage: Lint
|
|
displayName: "Lint"
|
|
jobs:
|
|
- job: Lint
|
|
pool: EKS-Pool
|
|
timeoutInMinutes: 30
|
|
continueOnError: false
|
|
steps: [] # type skill fills this in
|
|
|
|
- stage: SecurityScan
|
|
displayName: "Security Scan"
|
|
dependsOn: Lint
|
|
condition: succeeded()
|
|
jobs:
|
|
- job: SecurityScan
|
|
pool: EKS-Pool
|
|
timeoutInMinutes: 30
|
|
continueOnError: false
|
|
steps: [] # type skill fills this in
|
|
|
|
- stage: Build
|
|
displayName: "Build"
|
|
dependsOn: SecurityScan
|
|
condition: succeeded()
|
|
jobs:
|
|
- job: Build
|
|
pool: EKS-Pool
|
|
timeoutInMinutes: 30
|
|
continueOnError: false
|
|
steps: [] # type skill fills this in
|
|
|
|
- stage: DeployNonprod
|
|
displayName: "Deploy — Nonprod"
|
|
dependsOn: Build
|
|
condition: succeeded()
|
|
jobs:
|
|
- deployment: DeployNonprod
|
|
displayName: "Deploy to Nonprod"
|
|
pool: EKS-Pool
|
|
timeoutInMinutes: 30
|
|
environment: nonprod
|
|
strategy:
|
|
runOnce:
|
|
deploy:
|
|
steps: [] # type skill fills this in
|
|
|
|
- stage: DeployProd
|
|
displayName: "Deploy — Prod"
|
|
dependsOn: DeployNonprod
|
|
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/<trigger-branch>'))
|
|
jobs:
|
|
- deployment: DeployProd
|
|
displayName: "Deploy to Prod"
|
|
pool: EKS-Pool
|
|
timeoutInMinutes: 30
|
|
environment: prod # manual approval gate configured in ADO environment settings
|
|
strategy:
|
|
runOnce:
|
|
deploy:
|
|
steps: [] # type skill fills this in + git tag step below
|
|
```
|
|
|
|
## Prod tier pipelines
|
|
|
|
When `target tier` is `prod`, omit `DeployNonprod` entirely. The pipeline contains only `Lint` → `SecurityScan` → `Build` → `DeployProd` with the manual approval gate.
|
|
|
|
When `target tier` is `nonprod`, omit `DeployProd` entirely.
|
|
|
|
## Git tagging on prod deploy
|
|
|
|
Add this as the final step inside `DeployProd`'s steps (prod tier only):
|
|
|
|
```yaml
|
|
- script: |
|
|
git config user.email "azdo-pipeline@$(System.TeamProject)"
|
|
git config user.name "Azure DevOps Pipeline"
|
|
git remote set-url origin "https://x-token:$(System.AccessToken)@$(echo $BUILD_REPOSITORY_URI | sed 's|https://||')"
|
|
git tag $(Build.BuildNumber) $(Build.SourceVersion)
|
|
git push origin $(Build.BuildNumber)
|
|
displayName: "Tag commit with build number"
|
|
env:
|
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
|
BUILD_REPOSITORY_URI: $(Build.Repository.Uri)
|
|
```
|
|
|
|
## Secret handling patterns
|
|
|
|
Emit the correct block(s) based on declared secret sources:
|
|
|
|
### ADO variable groups
|
|
```yaml
|
|
variables:
|
|
- group: <variable-group-name>
|
|
```
|
|
Reference values as `$(VAR_NAME)` throughout the pipeline.
|
|
|
|
### AWS SSM Parameter Store
|
|
```yaml
|
|
- script: |
|
|
VALUE=$(aws ssm get-parameter \
|
|
--name "/myapp/mykey" \
|
|
--with-decryption \
|
|
--query "Parameter.Value" \
|
|
--output text)
|
|
echo "##vso[task.setvariable variable=MY_VAR;issecret=true]$VALUE"
|
|
displayName: "Fetch secret from SSM"
|
|
```
|
|
|
|
### AWS Secrets Manager
|
|
```yaml
|
|
- script: |
|
|
VALUE=$(aws secretsmanager get-secret-value \
|
|
--secret-id "myapp/mykey" \
|
|
--query "SecretString" \
|
|
--output text)
|
|
echo "##vso[task.setvariable variable=MY_VAR;issecret=true]$VALUE"
|
|
displayName: "Fetch secret from Secrets Manager"
|
|
```
|
|
|
|
### Vault / OpenBao
|
|
```yaml
|
|
- script: |
|
|
VALUE=$(vault kv get -field=mykey secret/myapp/mykey)
|
|
echo "##vso[task.setvariable variable=MY_VAR;issecret=true]$VALUE"
|
|
displayName: "Fetch secret from Vault"
|
|
env:
|
|
VAULT_ADDR: $(VAULT_ADDR)
|
|
VAULT_TOKEN: $(VAULT_TOKEN)
|
|
```
|
|
|
|
## Hard rules — always follow these
|
|
|
|
- `pool: EKS-Pool` on every job — no exceptions
|
|
- `timeoutInMinutes: 30` on every job
|
|
- `continueOnError: false` at **job level** on every job (not step level). Step-level `continueOnError` may be omitted.
|
|
- No secrets hardcoded in YAML — all via variable groups or runtime fetch
|
|
- Every stage and job has a `displayName:` set
|
|
- `pool: EKS-Pool` must appear at job level, not stage level, to ensure it applies correctly
|