Deployment Automation
Openlane uses a comprehensive automation system to manage configuration generation, Helm chart updates, and deployment workflows through Buildkite CI/CD pipelines and advanced configuration management.
Overview
The automation system bridges the gap between application configuration changes in the core repository and infrastructure deployment in the openlane-infra repository through intelligent automation that:
- Auto-generates Helm values and secret management resources from Go configuration
- Creates draft PRs showing configuration impact before core changes are merged
- Manages secrets through External Secrets Operator and GCP Secret Manager
- Updates Helm charts automatically with proper versioning and changelogs
- Provides visibility through Slack notifications and linked PR workflows
Buildkite Pipeline Structure
Core Pipeline (/.buildkite/pipeline.yaml)
The main pipeline includes several automation-focused steps:
Pre-check Phase
steps:
- label: ":yaml: generate config"
key: "generate_config"
command: ["task", "config:ci"]
- label: ":construction: create draft config PR"
key: "draft_pr_automation"
depends_on: "generate_config"
if: build.pull_request.id != null
- label: ":helm: update helm chart"
key: "helm_automation"
depends_on: "generate_config"
if: build.branch == "main"
Configuration Generation
The config:ci task runs the schema generator that:
- Analyzes Go structs for configuration fields and sensitive data
- Generates Helm values with proper schema annotations and comments
- Creates External Secrets templates for automatic secret injection
- Produces PushSecret resources for manual secret management
- Applies domain inheritance for environment-specific deployments
Draft PR Workflow
For pull requests, the system creates draft PRs in the infrastructure repository:
# Draft PR creation logic
if git diff --name-only | grep -E "(config/|pkg/.*config\.go|jsonschema/)" > /dev/null; then
create_draft_infrastructure_pr
link_prs_with_comments
send_notification_if_needed
fi
Automation Scripts
Core Scripts
helm-automation.sh- Main Helm chart update automationdraft-pr-automation.sh- Creates draft PRs for configuration previewspost-merge-pr-automation.sh- Finalizes PRs after core changes mergecleanup-draft-prs.sh- Cleans up stale draft PRsimage-tag-automation.sh- Updates image tags for releases
Library Functions
Located in .buildkite/lib/:
common.sh- Shared utilities and logging functionshelm.sh- Helm-specific operations and chart managementgithub.sh- GitHub API interactions and PR managementtemplates.sh- Template processing and substitution
Configuration Management System
Automatic Configuration Generation
The configuration system uses Go struct reflection to automatically:
Detect Configuration Fields
type Config struct {
APIKey string `json:"apiKey" koanf:"apiKey" sensitive:"true"`
Domain string `json:"domain" koanf:"domain" domain:"inherit"`
Port int `json:"port" koanf:"port" default:"8080"`
}
Generate Helm Values
# Auto-generated from Go structs
core:
apiKey: "" # managed via external secrets
domain: "" # inherits from global domain
port: 8080 # default value from struct
Create Secret Management Resources
# External Secret template
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: core-apikey
spec:
secretStoreRef:
name: gcp-secretstore
kind: ClusterSecretStore
target:
name: core-secrets
template:
data:
CORE_APIKEY: "{{ .apikey }}"
data:
- secretKey: apikey
remoteRef:
key: core-apikey
Domain Inheritance System
Global domain configuration automatically populates related fields:
Configuration
export CORE_DOMAIN=theopenlane.io
Automatic Field Population
SessionDomain→theopenlane.ioAPIEndpoint→https://api.theopenlane.io(with prefix)WebhookURL→https://api.theopenlane.io/v1/webhook(with prefix and suffix)
Generated Helm Templates
CORE_SUBSCRIPTION_STRIPEWEBHOOKURL:
{{- if .Values.core.subscription.stripeWebhookURL }}
{{ .Values.core.subscription.stripeWebhookURL }}
{{- else if .Values.domain }}
"https://api.{{ .Values.domain }}/v1/stripe/webhook"
{{- else }}
"https://api.openlane.com/v1/stripe/webhook"
{{- end }}
Secret Management Automation
External Secrets Integration
The system automatically detects fields marked with sensitive:"true" and:
- Excludes them from plain-text Helm values
- Generates External Secret resources for automated injection
- Creates PushSecret resources for manual secret management
- Configures environment variable mapping
Secret Naming Convention
For a sensitive field objectStorage.accessKey:
- Environment Variable:
CORE_OBJECTSTORAGE_ACCESSKEY - Secret Name:
core-objectstorage-accesskey - Remote Key:
core-objectstorage-accesskey(in GCP Secret Manager)
PushSecret Resources
Individual files for manual secret management:
# config/pushsecrets/core-objectstorage-accesskey.yaml
apiVersion: v1
kind: Secret
metadata:
name: core-objectstorage-accesskey
namespace: openlane-secret-push
data:
value: <base64-encoded-secret>
---
apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: core-objectstorage-accesskey
namespace: openlane-secret-push
spec:
secretStoreRef:
name: gcp-secretstore
kind: ClusterSecretStore
selector:
secret:
name: core-objectstorage-accesskey
data:
- match:
secretKey: value
remoteRef:
remoteKey: core-objectstorage-accesskey
Draft PR Workflow
Problem Solved
Before the draft PR system:
- ❌ Core changes merged without seeing infrastructure impact
- ❌ Configuration issues discovered during deployment
- ❌ Bad development loop: merge first, see problems later
Solution
The draft PR workflow provides full visibility into configuration changes:
1. Developer Opens Core PR
git checkout -b feature/add-redis-config
# Edit configuration files
git commit -m "Add Redis caching configuration"
git push origin feature/add-redis-config
gh pr create --title "Add Redis caching support"
2. Automatic Draft PR Creation
- Detects configuration changes in the core PR
- Generates new configuration files
- Creates draft PR in openlane-infra repository
- Preserves existing Kubernetes settings (replicas, images, etc.)
3. PR Linking
Both PRs receive comments linking them together:
Core PR Comment:
## 🔗 Related Infrastructure Changes
A draft PR has been automatically created:
**🚧 Draft Infrastructure PR:** https://github.com/theopenlane/openlane-infra/pull/789
### 📋 Review Process
1. Review both PRs together
2. Merge this core PR first
3. Review and merge the infrastructure PR
4. Post-Merge Automation
After core PR merge:
- Draft PR automatically converts to ready for review
- Final configuration changes applied
- Chart version incremented
- Slack notification sent to infrastructure team
Configuration Change Detection
The system triggers draft PR creation for changes to:
Direct Configuration Files:
config/helm-values.yamlconfig/configmap.yamlconfig/external-secrets/config/pushsecrets/
Configuration Source Files:
pkg/objects/config.gointernal/ent/entconfig/config.gopkg/entitlements/config.go- Schema generator changes
Helm Chart Automation
Chart Update Process
When configuration changes are merged to main:
- Clone the openlane-infra repository
- Apply configuration changes using intelligent merging
- Preserve Kubernetes-specific settings (replicas, images, etc.)
- Update only core application configuration
- Increment chart version automatically
- Generate changelog with change summary
- Create pull request with detailed description
Intelligent Merging
Instead of overwriting the entire values.yaml:
# Preserve Kubernetes configuration
yq eval '. as $target | load("generated-core.yaml") as $core |
$target | .core = $core.core |
.externalSecrets = $core.externalSecrets' values.yaml
Merged Sections (replaced):
core.*- Application configurationexternalSecrets.*- Secret management
Preserved Sections (maintained):
replicaCount,image.*,service.*ingress.*,resources.*,nodeSelector.*- Custom organizational settings
Chart Versioning
Automatic version increments follow semantic versioning:
# Current version: 1.2.3
# Configuration changes → 1.2.4 (patch increment)
# New features → 1.3.0 (minor increment)
# Breaking changes → 2.0.0 (major increment)
Slack Integration
Notification Strategy
- Silent Drafts: Draft PR creation doesn't send notifications
- Ready Notifications: Only notify when PRs need review/action
- Template-Based: External JSON templates for consistency
Webhook Setup
-
Create Slack Webhook:
- Go to Slack workspace settings
- Navigate to "Incoming webhooks"
- Create webhook for desired channel
- Copy webhook URL
-
Configure Buildkite Secret:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
Notification Examples
Helm Chart Update Notification:
{
"text": "🤖 Helm Chart Update Required",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Changes Made:*\n✅ Updated Helm values.yaml\n🔐 Updated External Secrets templates\n📈 Bumped chart version to 1.2.34"
}
}
]
}
Testing and Validation
Automated Testing
Comprehensive test suite validates automation functionality:
# Run all automation tests
./.buildkite/tests/helm-automation.bats
# Test specific components
./.buildkite/tests/draft-pr-automation.bats
./.buildkite/tests/slack-utils.bats
Local Testing
Test automation scripts locally:
# Test syntax and logic
./.buildkite/test-helm-automation.sh dry-run
# Test with local repository
./.buildkite/test-helm-automation.sh local-repo --verbose
# Test Slack webhooks
SLACK_WEBHOOK_URL=https://hooks.slack.com/... \
./.buildkite/test-helm-automation.sh webhook-test
Manual Configuration Testing
# Regenerate configuration files
go run ./jsonschema/schema_generator.go
# Validate generated Helm values
helm lint ./config/helm-values.yaml
# Test External Secrets templates
kubectl apply --dry-run=client -f config/external-secrets/
Security Considerations
Secret Handling
- Separation of Concerns: Sensitive values never stored in plain text
- Secret Store Integration: All secrets managed through GCP Secret Manager
- Individual Secret Control: Each secret can be enabled/disabled independently
- Secure Namespacing: PushSecrets use dedicated namespace
Access Control
- GitHub Tokens: Limited scope for repository access
- GCP Service Accounts: Minimal permissions for Secret Manager
- Buildkite Secrets: Encrypted storage with access controls
Audit Trail
- Git History: All configuration changes tracked in version control
- PR Comments: Detailed change summaries and approval workflows
- Slack Notifications: Team awareness and audit logging
- Build Logs: Complete automation execution history
Troubleshooting
Common Issues
Draft PR Not Created
Symptoms: Configuration changes don't trigger draft PR Solutions:
- Check if configuration files were actually changed
- Verify GitHub token permissions
- Review build pipeline logs for errors
Configuration Not Applied
Symptoms: Changes don't appear in Helm chart Solutions:
- Ensure
config:citask completed successfully - Check for merge conflicts in values.yaml
- Verify schema generator output
Slack Notifications Missing
Symptoms: No notifications despite PR creation Solutions:
- Verify webhook URL configuration
- Check Slack workspace permissions
- Test webhook with manual payload
Debugging Commands
# Check configuration generation
go run ./jsonschema/schema_generator.go
# Validate Helm chart changes
helm template ./charts/openlane --values values.yaml
# Test External Secrets
kubectl apply --dry-run=client -f config/external-secrets/
# Check automation logs
buildkite-agent artifact download "*.log"
Manual Overrides
Skip Automation
Add to commit message:
[skip-automation] Manual configuration changes
This change requires manual infrastructure updates.
Force Draft PR
Add to commit message:
[force-draft-pr] Configuration comment updates
Create draft PR even for minor changes.