Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,4 @@ jobs:
dry-run: ${{ inputs.dry-run }}
environment: ${{ needs.prepare.outputs.github-environment }}
ref: ${{ needs.prepare.outputs.ref }}
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
SITE_OVERRIDES: ${{ secrets.SITE_OVERRIDES }}
secrets: inherit
103 changes: 103 additions & 0 deletions .pipelines/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# CI pipeline: Unit tests and manifest validation
# Equivalent to .github/workflows/ci.yaml

trigger:
branches:
include: [main]
paths:
include:
- siteops/**
- workspaces/**
- tests/**
- pyproject.toml

pr:
branches:
include: [main]
paths:
include:
- siteops/**
- workspaces/**
- tests/**
- pyproject.toml

pool:
vmImage: ubuntu-latest

jobs:
- job: test
displayName: Unit Tests
steps:
- checkout: self
fetchDepth: 1
persistCredentials: false

- template: templates/setup-siteops.yaml
parameters:
installDev: true

- script: >
pytest tests/ -v
--cov=siteops
--cov-report=term-missing
--cov-report=xml:coverage.xml
--junitxml=test-results.xml
displayName: Run unit tests

- task: PublishTestResults@2
displayName: Publish test results
condition: succeededOrFailed()
inputs:
testResultsFormat: JUnit
testResultsFiles: test-results.xml
testRunTitle: Site Ops Unit Tests

- task: PublishCodeCoverageResults@2
displayName: Publish code coverage
condition: succeededOrFailed()
inputs:
summaryFileLocation: coverage.xml

- job: validate
displayName: Validate Manifests
steps:
- checkout: self
fetchDepth: 1
persistCredentials: false

- template: templates/setup-siteops.yaml

- script: |
WORKSPACE_DIR="workspaces/iot-operations"
MANIFESTS=$(find "$WORKSPACE_DIR/manifests" -name "*.yaml" -o -name "*.yml" 2>/dev/null | tr '\n' ' ' || echo "")

if [[ -z "$MANIFESTS" ]]; then
echo "##vso[task.logissue type=warning]No manifests found in $WORKSPACE_DIR/manifests"
exit 0
fi

echo "Found manifests: $MANIFESTS"

SUMMARY_FILE="$(Build.ArtifactStagingDirectory)/validation-summary.md"
echo "## Deployment Plans" > "$SUMMARY_FILE"
EXIT_CODE=0

for manifest in $MANIFESTS; do
manifest_rel="${manifest#$WORKSPACE_DIR/}"
echo "##[group]Validating $manifest"
echo "### $(basename $manifest)" >> "$SUMMARY_FILE"
echo '```' >> "$SUMMARY_FILE"

if siteops -w "$WORKSPACE_DIR" validate "$manifest_rel" -v 2>&1 | tee -a "$SUMMARY_FILE"; then
echo "✓ Valid"
else
EXIT_CODE=1
fi

echo '```' >> "$SUMMARY_FILE"
echo "##[endgroup]"
done

echo "##vso[task.uploadsummary]$SUMMARY_FILE"
exit $EXIT_CODE
displayName: Validate manifests and show plans
119 changes: 119 additions & 0 deletions .pipelines/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Deploy pipeline: Manual deployment trigger
# Equivalent to .github/workflows/deploy.yaml

name: $(Date:yyyyMMdd)$(Rev:.r) · ${{ parameters.environment }} · ${{ parameters.manifest }}${{ iif(parameters.dryRun, ' (dry-run)', '') }}

trigger: none
pr: none

parameters:
- name: workspace
displayName: Workspace
type: string
default: iot-operations
values: [iot-operations]

- name: manifest
displayName: Manifest
type: string
default: aio-install
values: [aio-install, opc-ua-solution]

- name: environment
displayName: Target environment
type: string
default: dev
values: [dev, staging, prod]

- name: selector
displayName: Additional site selector (e.g., country=US,name=seattle-dev)
type: string
default: ' '

- name: dryRun
displayName: Dry run (preview only)
type: boolean
default: false

# Per-environment lookup tables. To split resources per environment,
# change the values below — no structural changes needed.
- name: serviceConnections
displayName: Service connection per environment
type: object
default:
dev: azure-siteops
staging: azure-siteops
prod: azure-siteops

- name: secretGroups
displayName: Variable group per environment
type: object
default:
dev: siteops-secrets
staging: siteops-secrets
prod: siteops-secrets

variables:
- group: ${{ parameters.secretGroups[parameters.environment] }}
- name: PYTHONUNBUFFERED
value: '1'

pool:
vmImage: ubuntu-latest

stages:
- stage: prepare
displayName: Validate and Resolve
jobs:
- job: resolve
displayName: Prepare Deployment
steps:
- checkout: self
fetchDepth: 1
persistCredentials: false

- template: templates/setup-siteops.yaml

- script: |
MANIFEST_PATH="workspaces/${{ parameters.workspace }}/manifests/${{ parameters.manifest }}.yaml"
if [[ ! -f "$MANIFEST_PATH" ]]; then
echo "##vso[task.logissue type=error]Manifest not found: $MANIFEST_PATH"
echo ""
echo "Available manifests:"
find "workspaces/${{ parameters.workspace }}/manifests" -name "*.yaml" -o -name "*.yml" 2>/dev/null || echo " (none found)"
exit 1
fi
echo "✓ Manifest found: $MANIFEST_PATH"
displayName: Validate manifest exists

- script: |
SELECTOR="$(echo "${{ parameters.selector }}" | xargs)"
if [ -n "$SELECTOR" ]; then
COMBINED="environment=${{ parameters.environment }},$SELECTOR"
else
COMBINED="environment=${{ parameters.environment }}"
fi

SUMMARY_FILE="$(Build.ArtifactStagingDirectory)/deploy-config.md"
echo "### Deployment Configuration" > "$SUMMARY_FILE"
echo "| Setting | Value |" >> "$SUMMARY_FILE"
echo "|---------|-------|" >> "$SUMMARY_FILE"
echo "| Workspace | \`workspaces/${{ parameters.workspace }}\` |" >> "$SUMMARY_FILE"
echo "| Manifest | \`manifests/${{ parameters.manifest }}.yaml\` |" >> "$SUMMARY_FILE"
echo "| Environment | \`${{ parameters.environment }}\` |" >> "$SUMMARY_FILE"
echo "| Selector | \`$COMBINED\` |" >> "$SUMMARY_FILE"
echo "| Dry Run | \`${{ parameters.dryRun }}\` |" >> "$SUMMARY_FILE"
echo "##vso[task.uploadsummary]$SUMMARY_FILE"
displayName: Write deployment summary

- template: templates/siteops-deploy.yaml
parameters:
serviceConnection: ${{ parameters.serviceConnections[parameters.environment] }}
workspace: workspaces/${{ parameters.workspace }}
manifest: manifests/${{ parameters.manifest }}.yaml
environment: ${{ parameters.environment }}
${{ if not(in(parameters.selector, '', ' ')) }}:
selector: environment=${{ parameters.environment }},${{ parameters.selector }}
${{ else }}:
selector: environment=${{ parameters.environment }}
dryRun: ${{ parameters.dryRun }}
44 changes: 44 additions & 0 deletions .pipelines/templates/setup-siteops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Steps template: Install Python and Site Ops
# Equivalent to .github/actions/setup-siteops/action.yaml

parameters:
- name: pythonVersion
displayName: Python version
type: string
default: '3.11'
- name: installDev
displayName: Include dev dependencies (pytest, pytest-cov)
type: boolean
default: false

steps:
- task: UsePythonVersion@0
displayName: Set up Python ${{ parameters.pythonVersion }}
inputs:
versionSpec: ${{ parameters.pythonVersion }}

- script: echo "##vso[task.setvariable variable=PIP_CACHE_DIR]$(pip cache dir)"
displayName: Resolve pip cache directory

- task: Cache@2
displayName: Cache pip packages
inputs:
key: pip | "$(Agent.OS)" | ${{ parameters.installDev }} | pyproject.toml
path: $(PIP_CACHE_DIR)
restoreKeys: |
pip | "$(Agent.OS)" | ${{ parameters.installDev }}
pip | "$(Agent.OS)"

- script: |
pip install --upgrade pip
if [ "$INSTALL_DEV" = "True" ]; then
pip install -e ".[dev]"
else
pip install -e .
fi
displayName: Install Site Ops
env:
INSTALL_DEV: ${{ parameters.installDev }}

- script: siteops --version
displayName: Verify Site Ops installation
Loading