Repository Template System & GitHub Automation
Complete automation of GitHub repository creation from templates, including CI/CD workflow injection, branch protection setup, and parameterized pipeline deployment for client onboarding.
Repository Template System & GitHub Automation
Automated GitHub Repository Creation
This post covers the complete automation of GitHub repository creation from templates, including CI/CD workflow injection, branch protection setup, and parameterized pipeline deployment.
Overview
Step 2 of our automated client onboarding system focuses on creating standardised GitHub repositories from templates. This ensures every client gets a consistent, production-ready codebase with pre-configured CI/CD workflows, security policies, and deployment automation.
Step 2: Bootstrapping GitHub Repositories
The repository automation system creates a complete development environment for each client using GitHub's template repository feature combined with API automation.
Template Repository Structure
First, we maintain a master template repository with the standardised structure:
client-template-repository/
├── .github/
│ ├── workflows/
│ │ ├── adf-ci-cd.yml
│ │ ├── powerbi-deployment.yml
│ │ └── fabric-sync.yml
│ ├── ISSUE_TEMPLATE/
│ └── dependabot.yml
├── adf/
│ ├── pipeline/
│ │ └── client-data-ingestion.json
│ ├── dataset/
│ │ ├── http-source.json
│ │ └── onelake-sink.json
│ ├── linkedService/
│ │ ├── client-api.json
│ │ ├── keyvault.json
│ │ └── fabric-lakehouse.json
│ ├── trigger/
│ │ └── monthly-schedule.json
│ └── ARMTemplateForFactory.json
├── infrastructure/
│ ├── bicep/
│ │ ├── main.bicep
│ │ ├── keyvault.bicep
│ │ └── datafactory.bicep
│ └── parameters/
│ ├── dev.parameters.json
│ ├── test.parameters.json
│ └── prod.parameters.json
├── powerbi/
│ ├── reports/
│ │ └── client-dashboard-template.pbix
│ ├── datasets/
│ └── deployment/
├── scripts/
│ ├── pre-deployment.ps1
│ ├── post-deployment.ps1
│ └── utilities/
├── tests/
│ ├── unit/
│ ├── integration/
│ └── data-quality/
└── docs/
├── deployment-guide.md
└── client-requirements.md
Automated Repository Creation
The PowerShell automation uses GitHub's REST API to create repositories from templates:
function New-ClientRepository {
param(
[Parameter(Mandatory = $true)]
[string]$ClientName,
[Parameter(Mandatory = $true)]
[string]$GitHubOrg,
[Parameter(Mandatory = $true)]
[string]$GitHubToken,
[Parameter(Mandatory = $true)]
[string]$TemplateRepo = "client-data-platform-template"
)
Write-Host "Creating GitHub repository for client: $ClientName" -ForegroundColor Green
# Set up headers for GitHub API
$headers = @{
"Authorization" = "Bearer $GitHubToken"
"Accept" = "application/vnd.github.v3+json"
"User-Agent" = "PowerShell-ClientOnboarding"
}
# Repository creation payload
$newRepoName = "$ClientName-data-platform".ToLower()
$payload = @{
name = $newRepoName
description = "Automated data platform for $ClientName"
private = $true
include_all_branches = $false
} | ConvertTo-Json
try {
# Create repository from template
$createUri = "https://api.github.com/repos/$GitHubOrg/$TemplateRepo/generate"
$response = Invoke-RestMethod -Uri $createUri -Method POST -Headers $headers -Body $payload
Write-Host " Repository created: $($response.html_url)" -ForegroundColor Green
return $response
}
catch {
Write-Error "Failed to create repository: $($_.Exception.Message)"
throw
}
}
Parameter Injection and Customisation
After repository creation, we inject client-specific parameters into all configuration files:
function Update-ClientParameters {
param(
[Parameter(Mandatory = $true)]
[string]$RepoPath,
[Parameter(Mandatory = $true)]
[hashtable]$ClientConfig
)
Write-Host "Injecting client parameters into repository files" -ForegroundColor Yellow
# Define parameter mappings
$parameterMappings = @{
"{{CLIENT_NAME}}" = $ClientConfig.ClientName
"{{CLIENT_API_ENDPOINT}}" = $ClientConfig.ApiEndpoint
"{{AZURE_SUBSCRIPTION_ID}}" = $ClientConfig.SubscriptionId
"{{RESOURCE_GROUP_NAME}}" = $ClientConfig.ResourceGroupName
"{{DATA_FACTORY_NAME}}" = $ClientConfig.DataFactoryName
"{{KEY_VAULT_NAME}}" = $ClientConfig.KeyVaultName
"{{FABRIC_WORKSPACE_DEV}}" = "$($ClientConfig.ClientName)-Dev"
"{{FABRIC_WORKSPACE_TEST}}" = "$($ClientConfig.ClientName)-Test"
"{{FABRIC_WORKSPACE_PROD}}" = "$($ClientConfig.ClientName)-Prod"
}
# File patterns to update
$filePatterns = @(
"*.json",
"*.yml",
"*.yaml",
"*.bicep",
"*.ps1",
"*.md"
)
foreach ($pattern in $filePatterns) {
$files = Get-ChildItem -Path $RepoPath -Filter $pattern -Recurse
foreach ($file in $files) {
$content = Get-Content -Path $file.FullName -Raw
foreach ($placeholder in $parameterMappings.Keys) {
$content = $content -replace [regex]::Escape($placeholder), $parameterMappings[$placeholder]
}
Set-Content -Path $file.FullName -Value $content -NoNewline
Write-Host " Updated: $($file.Name)" -ForegroundColor Gray
}
}
}
CI/CD Workflow Configuration
Azure Data Factory CI/CD Pipeline
The template includes a comprehensive ADF deployment workflow:
name: Azure Data Factory CI/CD
on:
push:
branches: [ main, develop ]
paths: [ 'adf/**' ]
pull_request:
branches: [ main ]
paths: [ 'adf/**' ]
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
RESOURCE_GROUP_NAME: ${{ secrets.RESOURCE_GROUP_NAME }}
DATA_FACTORY_NAME: ${{ secrets.DATA_FACTORY_NAME }}
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Validate ADF Resources
run: |
az datafactory pipeline list \
--factory-name $DATA_FACTORY_NAME \
--resource-group $RESOURCE_GROUP_NAME
- name: Run Data Quality Tests
run: |
python tests/data-quality/validate_schemas.py
python tests/data-quality/check_data_lineage.py
deploy-dev:
needs: validate
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment: development
steps:
- uses: actions/checkout@v4
- name: Deploy to Development
run: |
az deployment group create \
--resource-group $RESOURCE_GROUP_NAME \
--template-file infrastructure/bicep/main.bicep \
--parameters @infrastructure/parameters/dev.parameters.json \
--parameters clientName=${{ github.event.repository.name }}
deploy-prod:
needs: validate
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to Production
run: |
az deployment group create \
--resource-group $RESOURCE_GROUP_NAME \
--template-file infrastructure/bicep/main.bicep \
--parameters @infrastructure/parameters/prod.parameters.json \
--parameters clientName=${{ github.event.repository.name }}
Power BI Deployment Pipeline
Automated Power BI report deployment and workspace management:
name: Power BI Deployment
on:
push:
branches: [ main ]
paths: [ 'powerbi/**' ]
jobs:
deploy-powerbi:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install Power BI PowerShell
shell: powershell
run: |
Install-Module -Name MicrosoftPowerBIMgmt -Force -Scope CurrentUser
- name: Connect to Power BI
shell: powershell
run: |
$securePassword = ConvertTo-SecureString ${{ secrets.POWERBI_PASSWORD }} -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential(${{ secrets.POWERBI_USERNAME }}, $securePassword)
Connect-PowerBIServiceAccount -Credential $credential
- name: Deploy Reports
shell: powershell
run: |
$workspaceName = "${{ github.event.repository.name }}-Prod"
$workspace = Get-PowerBIWorkspace -Name $workspaceName
Get-ChildItem -Path "powerbi/reports/*.pbix" | ForEach-Object {
New-PowerBIReport -Path $_.FullName -WorkspaceId $workspace.Id -Name $_.BaseName
}
Branch Protection and Security
Automated Branch Protection Setup
function Set-BranchProtection {
param(
[Parameter(Mandatory = $true)]
[string]$RepoFullName,
[Parameter(Mandatory = $true)]
[string]$GitHubToken
)
$headers = @{
"Authorization" = "Bearer $GitHubToken"
"Accept" = "application/vnd.github.v3+json"
}
# Main branch protection
$mainProtection = @{
required_status_checks = @{
strict = $true
contexts = @("validate", "security-scan")
}
enforce_admins = $true
required_pull_request_reviews = @{
required_approving_review_count = 2
dismiss_stale_reviews = $true
require_code_owner_reviews = $true
}
restrictions = $null
allow_force_pushes = $false
allow_deletions = $false
} | ConvertTo-Json -Depth 10
$uri = "https://api.github.com/repos/$RepoFullName/branches/main/protection"
Invoke-RestMethod -Uri $uri -Method PUT -Headers $headers -Body $mainProtection
Write-Host " Branch protection configured for main branch" -ForegroundColor Green
}
Security Scanning Integration
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Secret Scanning
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
head: HEAD
Repository Secrets Management
Automated Secrets Injection
function Set-RepositorySecrets {
param(
[Parameter(Mandatory = $true)]
[string]$RepoFullName,
[Parameter(Mandatory = $true)]
[string]$GitHubToken,
[Parameter(Mandatory = $true)]
[hashtable]$ClientSecrets
)
$headers = @{
"Authorization" = "Bearer $GitHubToken"
"Accept" = "application/vnd.github.v3+json"
}
# Get repository public key for secret encryption
$keyUri = "https://api.github.com/repos/$RepoFullName/actions/secrets/public-key"
$publicKey = Invoke-RestMethod -Uri $keyUri -Headers $headers
foreach ($secretName in $ClientSecrets.Keys) {
$encryptedValue = Protect-GitHubSecret -PlainText $ClientSecrets[$secretName] -PublicKey $publicKey.key
$secretPayload = @{
encrypted_value = $encryptedValue
key_id = $publicKey.key_id
} | ConvertTo-Json
$secretUri = "https://api.github.com/repos/$RepoFullName/actions/secrets/$secretName"
Invoke-RestMethod -Uri $secretUri -Method PUT -Headers $headers -Body $secretPayload
Write-Host " Secret configured: $secretName" -ForegroundColor Gray
}
}
function Protect-GitHubSecret {
param(
[string]$PlainText,
[string]$PublicKey
)
# Implement libsodium sealed box encryption
# This is a simplified example - use proper encryption library
Add-Type -AssemblyName System.Security
$bytes = [System.Text.Encoding]::UTF8.GetBytes($PlainText)
$publicKeyBytes = [Convert]::FromBase64String($PublicKey)
# Use libsodium or similar for actual encryption
# Return base64 encoded encrypted value
return [Convert]::ToBase64String($bytes)
}
Complete Repository Setup Function
function Initialize-ClientRepository {
param(
[Parameter(Mandatory = $true)]
[string]$ClientName,
[Parameter(Mandatory = $true)]
[string]$GitHubOrg,
[Parameter(Mandatory = $true)]
[string]$GitHubToken,
[Parameter(Mandatory = $true)]
[hashtable]$ClientConfig
)
Write-Host "Initializing GitHub repository for $ClientName" -ForegroundColor Cyan
try {
# Step 1: Create repository from template
$repo = New-ClientRepository -ClientName $ClientName -GitHubOrg $GitHubOrg -GitHubToken $GitHubToken
# Step 2: Clone repository locally for modifications
$tempPath = Join-Path $env:TEMP "client-repos"
$repoPath = Join-Path $tempPath $repo.name
if (Test-Path $repoPath) { Remove-Item $repoPath -Recurse -Force }
New-Item -Path $tempPath -ItemType Directory -Force | Out-Null
git clone $repo.clone_url $repoPath
Set-Location $repoPath
# Step 3: Update parameters in all files
Update-ClientParameters -RepoPath $repoPath -ClientConfig $ClientConfig
# Step 4: Commit and push changes
git add .
git commit -m "Initial client configuration for $ClientName"
git push origin main
# Step 5: Set up branch protection
Set-BranchProtection -RepoFullName $repo.full_name -GitHubToken $GitHubToken
# Step 6: Configure repository secrets
$secrets = @{
"AZURE_SUBSCRIPTION_ID" = $ClientConfig.SubscriptionId
"AZURE_CREDENTIALS" = $ClientConfig.AzureCredentials
"RESOURCE_GROUP_NAME" = $ClientConfig.ResourceGroupName
"DATA_FACTORY_NAME" = $ClientConfig.DataFactoryName
"KEY_VAULT_NAME" = $ClientConfig.KeyVaultName
}
Set-RepositorySecrets -RepoFullName $repo.full_name -GitHubToken $GitHubToken -ClientSecrets $secrets
Write-Host "Repository setup completed successfully!" -ForegroundColor Green
Write-Host "Repository URL: $($repo.html_url)" -ForegroundColor Yellow
return @{
Repository = $repo
LocalPath = $repoPath
Success = $true
}
}
catch {
Write-Error "Repository setup failed: $($_.Exception.Message)"
return @{
Repository = $null
LocalPath = $null
Success = $false
Error = $_.Exception.Message
}
}
}
Integration with Client Onboarding
This repository automation integrates seamlessly with the broader client onboarding process:
- Infrastructure provisioning creates the Azure resources
- Repository creation (this step) sets up the development environment
- Pipeline deployment configures the data ingestion workflows
- Fabric workspace setup creates the analytics environment
The result is a complete, production-ready data platform that's ready for immediate use by the client's development team.
Benefits and Outcomes
Consistency and Standardisation
- Uniform codebase structure across all clients
- Standardised CI/CD practices and security policies
- Consistent naming conventions and documentation
Reduced Time to Value
- Immediate development readiness - no setup time required
- Pre-configured security scanning and compliance checks
- Automated deployment pipelines from day one
Scalability and Maintenance
- Template-based approach allows easy updates across all clients
- Centralised security policies can be updated globally
- Automated dependency management through Dependabot
This automation transforms what used to be a 2-3 day manual setup process into a 5-minute automated deployment, while ensuring higher quality and consistency than manual processes could achieve.