provider-azuredevops is a comprehensive Crossplane Provider built using Upjet v2 that wraps the Terraform Azure DevOps Provider to enable declarative management of Azure DevOps resources via Kubernetes.
- 92 Managed Resources: Full coverage of the Terraform Azure DevOps provider (v1.4.0)
- GitOps-native: Declaratively manage Azure DevOps resources using Kubernetes manifests
- Crossplane-powered: Leverage Crossplane's composition and claim features for platform abstraction
- Production-ready: Built on the official Upjet v2 provider template with modern best practices
- Multi-arch support: ARM64 and AMD64 container images
| Category | Resources | Description |
|---|---|---|
| Agent | 2 | Agent pools and queues |
| Build | 6 | Build definitions, folders, permissions, and resource authorization |
| Git | 5 | Git repositories and permissions |
| Branch Policy | 8 | Branch policies (build validation, merge types, reviewers, etc.) |
| Repository Policy | 7 | Repository-level policies (case enforcement, file paths, size limits) |
| Service Endpoints | 28 | Service connections (Azure RM, AWS, GitHub, Kubernetes, Docker, etc.) |
| Check | 2 | Approval and check configurations |
| Group | 4 | Group management and membership |
| Team | 3 | Team management and administrators |
| Feed | 2 | Azure Artifacts feeds and permissions |
| Environment | 2 | Deployment environments and resource authorization |
| Variable Group | 1 | Pipeline variable groups |
| Wiki | 1 | Project wikis |
| Work Item | 1 | Work item tracking |
| Permissions | 4 | Project permissions and security |
| Other | 16 | Projects, users, pipeline settings, and more |
Total: 92 managed resources providing complete Azure DevOps infrastructure as code capability.
- Kubernetes cluster (1.24+)
- Crossplane installed (v1.14+)
- Azure DevOps organization with admin access
- Personal Access Token (PAT) with appropriate scopes
- Install the Provider:
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-azuredevops
spec:
package: ghcr.io/gontzalson/provider-azuredevops:latest
EOF- Wait for provider to become healthy:
kubectl wait --for=condition=Healthy provider/provider-azuredevops --timeout=300s- Configure Provider Authentication:
Create a Kubernetes secret with your Azure DevOps PAT:
kubectl create secret generic azuredevops-credentials \
--from-literal=credentials='{"pat":"YOUR_PAT_HERE","org_service_url":"https://dev.azure.com/YOUR_ORG"}' \
-n crossplane-system- Create a ProviderConfig:
apiVersion: azuredevops.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: azuredevops-credentials
key: credentials---
# Project
apiVersion: project.azuredevops.crossplane.io/v1alpha1
kind: Project
metadata:
name: my-project
spec:
forProvider:
name: My Awesome Project
description: Managed by Crossplane
visibility: private
versionControl: Git
workItemTemplate: Agile
providerConfigRef:
name: default
---
# Git Repository
apiVersion: git.azuredevops.crossplane.io/v1alpha1
kind: Repository
metadata:
name: my-repo
spec:
forProvider:
projectIdRef:
name: my-project
name: my-repository
initialization:
initType: Clean
providerConfigRef:
name: default
---
# Branch Policy - Build Validation
apiVersion: branchpolicy.azuredevops.crossplane.io/v1alpha1
kind: BuildValidation
metadata:
name: build-validation
spec:
forProvider:
projectIdRef:
name: my-project
enabled: true
blocking: true
settings:
displayName: CI Build Validation
buildDefinitionId: 123
queueOnSourceUpdateOnly: true
validDuration: 720
scope:
- repositoryIdRef:
name: my-repo
refName: refs/heads/main
matchType: Exact
providerConfigRef:
name: default
---
# Service Endpoint - Azure RM
apiVersion: serviceendpoint.azuredevops.crossplane.io/v1alpha1
kind: AzureRM
metadata:
name: azure-connection
spec:
forProvider:
projectIdRef:
name: my-project
servicePrincipalId: "00000000-0000-0000-0000-000000000000"
servicePrincipalKey: "your-sp-key"
tenantId: "00000000-0000-0000-0000-000000000000"
subscriptionId: "00000000-0000-0000-0000-000000000000"
subscriptionName: "My Subscription"
providerConfigRef:
name: default---
# Variable Group
apiVersion: variablegroup.azuredevops.crossplane.io/v1alpha1
kind: VariableGroup
metadata:
name: shared-vars
spec:
forProvider:
projectIdRef:
name: my-project
name: Shared Variables
description: Common variables
allowAccess: true
variable:
- name: environment
value: production
- name: region
value: westeurope
providerConfigRef:
name: default
---
# Build Definition
apiVersion: build.azuredevops.crossplane.io/v1alpha1
kind: Definition
metadata:
name: ci-pipeline
spec:
forProvider:
projectIdRef:
name: my-project
name: CI Pipeline
path: "\\CI"
repository:
repoType: TfsGit
repoIdRef:
name: my-repo
branchName: refs/heads/main
ymlPath: azure-pipelines.yml
variableGroups:
- variableGroupIdRef:
name: shared-vars
providerConfigRef:
name: defaultFor a complete list of all 92 managed resources and their schemas, see:
Common resource types include:
- Projects, teams, and groups
- Git repositories with branch policies
- Build definitions and pipelines
- Service endpoints (28 types!)
- Environments and checks
- Artifact feeds and permissions
- Work items and wikis
- Security and permissions
# Clone the repository
git clone https://github.com/gontzalson/provider-azuredevops
cd provider-azuredevops
# Generate CRDs and controllers
make generate
# Build the provider binary
make build
# Build and push multi-arch container images
make build.allprovider-azuredevops/
├── apis/ # Generated API types
├── config/ # Upjet configuration
│ ├── agent/ # Agent pool resources
│ ├── build/ # Build definition resources
│ ├── git/ # Git repository resources
│ ├── branchpolicy/ # Branch policy resources
│ ├── serviceendpoint/ # Service endpoint resources (28!)
│ ├── ... # 10 more resource groups
│ ├── external_name.go # External name configuration (92 resources)
│ └── provider.go # Main provider configuration
├── internal/controller/ # Generated controllers
├── package/crds/ # Generated CRDs (175 files: cluster + namespaced)
└── examples/ # Usage examples
Resources from the upstream Terraform provider are added in config/:
- Add external name mapping in
config/external_name.go - Create or update resource group config in
config/<group>/config.go - Register in
config/provider.go - Run
make generateto generate CRDs and controllers
This provider uses the Upjet v2 framework to automatically generate Crossplane Custom Resources from Terraform provider schemas:
Kubernetes API
↓
Crossplane Provider (this)
↓
Upjet v2 Framework
↓
Terraform Azure DevOps Provider (v1.4.0)
↓
Azure DevOps REST API
Key Components:
- 175 CRDs: Auto-generated from Terraform provider schema (92 resources × 2 scopes)
- Controllers: Reconcile Kubernetes resources with Azure DevOps state
- Terraform Workspace: Manages Terraform state for each managed resource
- IdentifierFromProvider: External name strategy for all resources
The provider has been tested in production-like environments:
- ✅ Crossplane v2.1.3 compatibility
- ✅ Kind cluster validation
- ✅ Multi-arch container images (ARM64, AMD64)
- ✅ All 92 resource types registered and available
- ✅ GitHub Container Registry publishing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Crossplane Slack: #providers channel
Apache 2.0 - see LICENSE file for details.
Built with ❤️ using Crossplane and Upjet v2
Providing complete Azure DevOps infrastructure as code with 92 managed resources