812 lines
20 KiB
Markdown
812 lines
20 KiB
Markdown
|
|
# Phoenix Operating Model - Implementation Examples
|
||
|
|
|
||
|
|
**Practical code examples and implementation patterns for Phoenix operating model**
|
||
|
|
|
||
|
|
This document provides real-world implementation examples for the Phoenix operating model, including entity creation, infrastructure provisioning, CI/CD integration, and deployment patterns.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Table of Contents
|
||
|
|
|
||
|
|
1. [Entity Creation Examples](#entity-creation-examples)
|
||
|
|
2. [Infrastructure as Code Examples](#infrastructure-as-code-examples)
|
||
|
|
3. [CI/CD Pipeline Examples](#cicd-pipeline-examples)
|
||
|
|
4. [Multi-Region Deployment Examples](#multi-region-deployment-examples)
|
||
|
|
5. [Promotion Flow Examples](#promotion-flow-examples)
|
||
|
|
6. [Integration Examples](#integration-examples)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Entity Creation Examples
|
||
|
|
|
||
|
|
### Example 1: Complete Setup for Sovereign Government
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Complete setup: Client → Tenant → Subscription → Environment
|
||
|
|
|
||
|
|
// Step 1: Create Client (Billing Profile)
|
||
|
|
const client = await phoenix.commercial.createClient({
|
||
|
|
name: "Government Agency A",
|
||
|
|
legalEntity: {
|
||
|
|
name: "Government Agency A",
|
||
|
|
jurisdiction: "Nation A",
|
||
|
|
registrationNumber: "GOV-001",
|
||
|
|
address: {
|
||
|
|
street: "123 Government St",
|
||
|
|
city: "Capital City",
|
||
|
|
country: "Nation A"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
invoicingConfig: {
|
||
|
|
format: "PDF",
|
||
|
|
frequency: "MONTHLY",
|
||
|
|
currency: "USD",
|
||
|
|
paymentTerms: "Net 30",
|
||
|
|
billingAddress: {
|
||
|
|
street: "123 Government St",
|
||
|
|
city: "Capital City",
|
||
|
|
country: "Nation A"
|
||
|
|
},
|
||
|
|
emailRecipients: ["billing@agency.gov"]
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Step 2: Create Tenant
|
||
|
|
const tenant = await phoenix.tenancy.createTenant({
|
||
|
|
name: "agency-tenant",
|
||
|
|
clientId: client.id,
|
||
|
|
primaryDomains: ["agency.gov", "agency.sankofa.nexus"],
|
||
|
|
identityProvider: {
|
||
|
|
type: "KEYCLOAK",
|
||
|
|
config: {},
|
||
|
|
ssoEnabled: true,
|
||
|
|
mfaRequired: true
|
||
|
|
},
|
||
|
|
rbacNamespace: "agency-namespace",
|
||
|
|
complianceProfile: {
|
||
|
|
standards: ["ISO_27001", "SOC_2", "FEDRAMP"]
|
||
|
|
},
|
||
|
|
dataResidencyFlags: [{
|
||
|
|
region: "region-a",
|
||
|
|
requirement: "REQUIRED",
|
||
|
|
enforcement: "HARD"
|
||
|
|
}],
|
||
|
|
multiRegionEnabled: true
|
||
|
|
});
|
||
|
|
|
||
|
|
// Step 3: Create Subscription
|
||
|
|
const subscription = await phoenix.subscription.createSubscription({
|
||
|
|
name: "production-subscription",
|
||
|
|
tenantId: tenant.id,
|
||
|
|
type: "PRODUCT",
|
||
|
|
serviceBundles: [
|
||
|
|
{
|
||
|
|
service: "COMPUTE",
|
||
|
|
enabled: true,
|
||
|
|
quotas: {
|
||
|
|
vcpu: 100,
|
||
|
|
memory: 512,
|
||
|
|
instances: 50
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
service: "STORAGE",
|
||
|
|
enabled: true,
|
||
|
|
quotas: {
|
||
|
|
total: 10000,
|
||
|
|
perInstance: 500
|
||
|
|
}
|
||
|
|
}
|
||
|
|
],
|
||
|
|
policyPacks: [{
|
||
|
|
name: "security-policy",
|
||
|
|
type: "SECURITY",
|
||
|
|
policies: [],
|
||
|
|
enforcement: "HARD"
|
||
|
|
}]
|
||
|
|
});
|
||
|
|
|
||
|
|
// Step 4: Create Environment
|
||
|
|
const environment = await phoenix.environment.createEnvironment({
|
||
|
|
name: "production-env",
|
||
|
|
subscriptionId: subscription.id,
|
||
|
|
type: "PROD",
|
||
|
|
networkIsolation: {
|
||
|
|
vpcId: "vpc-prod",
|
||
|
|
subnetIds: ["subnet-prod-1", "subnet-prod-2"],
|
||
|
|
firewallRules: []
|
||
|
|
},
|
||
|
|
dataIsolation: {
|
||
|
|
encryptionAtRest: true,
|
||
|
|
encryptionInTransit: true,
|
||
|
|
accessControls: []
|
||
|
|
},
|
||
|
|
deploymentPolicies: [{
|
||
|
|
name: "production-policy",
|
||
|
|
type: "POLICY_DRIVEN",
|
||
|
|
approvalRequired: true,
|
||
|
|
approvers: ["release-manager-1", "release-manager-2"]
|
||
|
|
}],
|
||
|
|
region: "region-a"
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log("Setup complete:", {
|
||
|
|
client: client.id,
|
||
|
|
tenant: tenant.id,
|
||
|
|
subscription: subscription.id,
|
||
|
|
environment: environment.id
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example 2: Multi-National Government Setup
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Setup for multi-national government with multiple regions
|
||
|
|
|
||
|
|
const client = await phoenix.commercial.createClient({
|
||
|
|
name: "International Government",
|
||
|
|
legalEntity: {
|
||
|
|
name: "International Government",
|
||
|
|
jurisdiction: "Multi-National",
|
||
|
|
address: { /* ... */ }
|
||
|
|
},
|
||
|
|
invoicingConfig: { /* ... */ }
|
||
|
|
});
|
||
|
|
|
||
|
|
// Create tenants per nation
|
||
|
|
const nations = ["Nation A", "Nation B", "Nation C"];
|
||
|
|
const tenants = await Promise.all(nations.map(nation =>
|
||
|
|
phoenix.tenancy.createTenant({
|
||
|
|
name: `tenant-${nation.toLowerCase().replace(' ', '-')}`,
|
||
|
|
clientId: client.id,
|
||
|
|
primaryDomains: [`${nation.toLowerCase().replace(' ', '')}.gov`],
|
||
|
|
identityProvider: {
|
||
|
|
type: "KEYCLOAK",
|
||
|
|
config: {},
|
||
|
|
ssoEnabled: true,
|
||
|
|
mfaRequired: true
|
||
|
|
},
|
||
|
|
rbacNamespace: `namespace-${nation.toLowerCase().replace(' ', '-')}`,
|
||
|
|
complianceProfile: {
|
||
|
|
standards: ["ISO_27001", "SOC_2"]
|
||
|
|
},
|
||
|
|
dataResidencyFlags: [{
|
||
|
|
region: `region-${nation.toLowerCase().replace(' ', '-')}`,
|
||
|
|
requirement: "REQUIRED",
|
||
|
|
enforcement: "HARD"
|
||
|
|
}],
|
||
|
|
multiRegionEnabled: true
|
||
|
|
})
|
||
|
|
));
|
||
|
|
|
||
|
|
// Create landing zones per region
|
||
|
|
const landingZones = await Promise.all(tenants.map((tenant, index) =>
|
||
|
|
phoenix.landingZones.createLandingZone({
|
||
|
|
name: `landing-zone-${nations[index].toLowerCase().replace(' ', '-')}`,
|
||
|
|
region: `region-${nations[index].toLowerCase().replace(' ', '-')}`,
|
||
|
|
tenantId: tenant.id,
|
||
|
|
sovereignCloud: true,
|
||
|
|
dataResidency: {
|
||
|
|
requirement: "REQUIRED",
|
||
|
|
enforcement: "HARD"
|
||
|
|
},
|
||
|
|
complianceProfile: {
|
||
|
|
standards: ["ISO_27001", "SOC_2", "FEDRAMP"]
|
||
|
|
}
|
||
|
|
})
|
||
|
|
));
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Infrastructure as Code Examples
|
||
|
|
|
||
|
|
### Terraform Example: Complete Phoenix Setup
|
||
|
|
|
||
|
|
```hcl
|
||
|
|
# terraform/phoenix-setup/main.tf
|
||
|
|
|
||
|
|
# Client (Billing Profile)
|
||
|
|
resource "phoenix_client" "government_agency" {
|
||
|
|
name = "Government Agency A"
|
||
|
|
|
||
|
|
legal_entity {
|
||
|
|
name = "Government Agency A"
|
||
|
|
jurisdiction = "Nation A"
|
||
|
|
registration_number = "GOV-001"
|
||
|
|
address {
|
||
|
|
street = "123 Government St"
|
||
|
|
city = "Capital City"
|
||
|
|
country = "Nation A"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
invoicing_config {
|
||
|
|
format = "PDF"
|
||
|
|
frequency = "MONTHLY"
|
||
|
|
currency = "USD"
|
||
|
|
payment_terms = "Net 30"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Tenant
|
||
|
|
resource "phoenix_tenant" "agency_tenant" {
|
||
|
|
name = "agency-tenant"
|
||
|
|
client_id = phoenix_client.government_agency.id
|
||
|
|
primary_domains = ["agency.gov", "agency.sankofa.nexus"]
|
||
|
|
|
||
|
|
identity_provider {
|
||
|
|
type = "KEYCLOAK"
|
||
|
|
sso_enabled = true
|
||
|
|
mfa_required = true
|
||
|
|
}
|
||
|
|
|
||
|
|
rbac_namespace = "agency-namespace"
|
||
|
|
|
||
|
|
compliance_profile {
|
||
|
|
standards = ["ISO_27001", "SOC_2", "FEDRAMP"]
|
||
|
|
}
|
||
|
|
|
||
|
|
data_residency_flags {
|
||
|
|
region = "region-a"
|
||
|
|
requirement = "REQUIRED"
|
||
|
|
enforcement = "HARD"
|
||
|
|
}
|
||
|
|
|
||
|
|
multi_region_enabled = true
|
||
|
|
}
|
||
|
|
|
||
|
|
# Subscription
|
||
|
|
resource "phoenix_subscription" "production" {
|
||
|
|
name = "production-subscription"
|
||
|
|
tenant_id = phoenix_tenant.agency_tenant.id
|
||
|
|
type = "PRODUCT"
|
||
|
|
|
||
|
|
service_bundles {
|
||
|
|
service = "COMPUTE"
|
||
|
|
enabled = true
|
||
|
|
quotas {
|
||
|
|
vcpu = 100
|
||
|
|
memory = 512
|
||
|
|
instances = 50
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
service_bundles {
|
||
|
|
service = "STORAGE"
|
||
|
|
enabled = true
|
||
|
|
quotas {
|
||
|
|
total = 10000
|
||
|
|
per_instance = 500
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
policy_packs {
|
||
|
|
name = "security-policy"
|
||
|
|
type = "SECURITY"
|
||
|
|
enforcement = "HARD"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Environment
|
||
|
|
resource "phoenix_environment" "production" {
|
||
|
|
name = "production-env"
|
||
|
|
subscription_id = phoenix_subscription.production.id
|
||
|
|
type = "PROD"
|
||
|
|
|
||
|
|
network_isolation {
|
||
|
|
vpc_id = "vpc-prod"
|
||
|
|
subnet_ids = ["subnet-prod-1", "subnet-prod-2"]
|
||
|
|
}
|
||
|
|
|
||
|
|
data_isolation {
|
||
|
|
encryption_at_rest = true
|
||
|
|
encryption_in_transit = true
|
||
|
|
}
|
||
|
|
|
||
|
|
deployment_policies {
|
||
|
|
name = "production-policy"
|
||
|
|
type = "POLICY_DRIVEN"
|
||
|
|
approval_required = true
|
||
|
|
approvers = ["release-manager-1", "release-manager-2"]
|
||
|
|
}
|
||
|
|
|
||
|
|
region = "region-a"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Landing Zone
|
||
|
|
resource "phoenix_landing_zone" "region_a" {
|
||
|
|
name = "landing-zone-region-a"
|
||
|
|
region = "region-a"
|
||
|
|
tenant_id = phoenix_tenant.agency_tenant.id
|
||
|
|
sovereign_cloud = true
|
||
|
|
|
||
|
|
data_residency {
|
||
|
|
requirement = "REQUIRED"
|
||
|
|
enforcement = "HARD"
|
||
|
|
allowed_regions = ["region-a"]
|
||
|
|
}
|
||
|
|
|
||
|
|
compliance {
|
||
|
|
standards = ["ISO_27001", "SOC_2", "FEDRAMP"]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pulumi Example: Multi-Region Deployment
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// pulumi/phoenix-multi-region/index.ts
|
||
|
|
|
||
|
|
import * as phoenix from "@sankofa/phoenix-sdk";
|
||
|
|
|
||
|
|
// Client
|
||
|
|
const client = new phoenix.Client("government-agency", {
|
||
|
|
name: "Government Agency A",
|
||
|
|
legalEntity: {
|
||
|
|
name: "Government Agency A",
|
||
|
|
jurisdiction: "Nation A",
|
||
|
|
address: {
|
||
|
|
street: "123 Government St",
|
||
|
|
city: "Capital City",
|
||
|
|
country: "Nation A"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
invoicingConfig: {
|
||
|
|
format: "PDF",
|
||
|
|
frequency: "MONTHLY",
|
||
|
|
currency: "USD"
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Multi-region tenants
|
||
|
|
const regions = ["region-a", "region-b", "region-c"];
|
||
|
|
const tenants = regions.map(region =>
|
||
|
|
new phoenix.Tenant(`tenant-${region}`, {
|
||
|
|
name: `tenant-${region}`,
|
||
|
|
clientId: client.id,
|
||
|
|
primaryDomains: [`${region}.agency.gov`],
|
||
|
|
identityProvider: {
|
||
|
|
type: "KEYCLOAK",
|
||
|
|
ssoEnabled: true,
|
||
|
|
mfaRequired: true
|
||
|
|
},
|
||
|
|
dataResidencyFlags: [{
|
||
|
|
region: region,
|
||
|
|
requirement: "REQUIRED",
|
||
|
|
enforcement: "HARD"
|
||
|
|
}],
|
||
|
|
multiRegionEnabled: true
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
// Landing zones per region
|
||
|
|
const landingZones = regions.map((region, index) =>
|
||
|
|
new phoenix.LandingZone(`landing-zone-${region}`, {
|
||
|
|
name: `landing-zone-${region}`,
|
||
|
|
region: region,
|
||
|
|
tenantId: tenants[index].id,
|
||
|
|
sovereignCloud: true,
|
||
|
|
dataResidency: {
|
||
|
|
requirement: "REQUIRED",
|
||
|
|
enforcement: "HARD"
|
||
|
|
}
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
export const clientId = client.id;
|
||
|
|
export const tenantIds = tenants.map(t => t.id);
|
||
|
|
export const landingZoneIds = landingZones.map(lz => lz.id);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CI/CD Pipeline Examples
|
||
|
|
|
||
|
|
### GitHub Actions: Policy-Driven Promotion
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# .github/workflows/promote.yml
|
||
|
|
|
||
|
|
name: Promote to Environment
|
||
|
|
|
||
|
|
on:
|
||
|
|
workflow_dispatch:
|
||
|
|
inputs:
|
||
|
|
from_env:
|
||
|
|
description: 'Source Environment'
|
||
|
|
required: true
|
||
|
|
type: choice
|
||
|
|
options:
|
||
|
|
- dev
|
||
|
|
- int
|
||
|
|
- uat
|
||
|
|
- staging
|
||
|
|
to_env:
|
||
|
|
description: 'Target Environment'
|
||
|
|
required: true
|
||
|
|
type: choice
|
||
|
|
options:
|
||
|
|
- int
|
||
|
|
- uat
|
||
|
|
- staging
|
||
|
|
- prod
|
||
|
|
artifact_id:
|
||
|
|
description: 'Artifact ID'
|
||
|
|
required: true
|
||
|
|
|
||
|
|
jobs:
|
||
|
|
promote:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- name: Checkout
|
||
|
|
uses: actions/checkout@v3
|
||
|
|
|
||
|
|
- name: Validate Promotion Policy
|
||
|
|
run: |
|
||
|
|
# Validate promotion is allowed
|
||
|
|
curl -X POST ${{ secrets.PHOENIX_API }}/api/v1/environment/promotions/validate \
|
||
|
|
-H "Authorization: Bearer ${{ secrets.PHOENIX_TOKEN }}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"fromEnvironment": "${{ github.event.inputs.from_env }}",
|
||
|
|
"toEnvironment": "${{ github.event.inputs.to_env }}",
|
||
|
|
"artifactId": "${{ github.event.inputs.artifact_id }}"
|
||
|
|
}'
|
||
|
|
|
||
|
|
- name: Request Promotion
|
||
|
|
id: promote
|
||
|
|
run: |
|
||
|
|
RESPONSE=$(curl -X POST ${{ secrets.PHOENIX_API }}/api/v1/environment/promotions \
|
||
|
|
-H "Authorization: Bearer ${{ secrets.PHOENIX_TOKEN }}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"artifactId": "${{ github.event.inputs.artifact_id }}",
|
||
|
|
"fromEnvironmentId": "${{ github.event.inputs.from_env }}",
|
||
|
|
"toEnvironmentId": "${{ github.event.inputs.to_env }}"
|
||
|
|
}')
|
||
|
|
echo "promotion_id=$(echo $RESPONSE | jq -r '.id')" >> $GITHUB_OUTPUT
|
||
|
|
|
||
|
|
- name: Check Approval Required
|
||
|
|
if: steps.promote.outputs.promotion_id != ''
|
||
|
|
run: |
|
||
|
|
APPROVAL_REQUIRED=$(curl -s ${{ secrets.PHOENIX_API }}/api/v1/environment/promotions/${{ steps.promote.outputs.promotion_id }} \
|
||
|
|
-H "Authorization: Bearer ${{ secrets.PHOENIX_TOKEN }}" | jq -r '.approvalRequired')
|
||
|
|
|
||
|
|
if [ "$APPROVAL_REQUIRED" = "true" ]; then
|
||
|
|
echo "Approval required. Waiting for approval..."
|
||
|
|
# Wait for approval
|
||
|
|
while true; do
|
||
|
|
STATUS=$(curl -s ${{ secrets.PHOENIX_API }}/api/v1/environment/promotions/${{ steps.promote.outputs.promotion_id }} \
|
||
|
|
-H "Authorization: Bearer ${{ secrets.PHOENIX_TOKEN }}" | jq -r '.status')
|
||
|
|
|
||
|
|
if [ "$STATUS" = "APPROVED" ]; then
|
||
|
|
echo "Promotion approved"
|
||
|
|
break
|
||
|
|
elif [ "$STATUS" = "REJECTED" ]; then
|
||
|
|
echo "Promotion rejected"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
sleep 10
|
||
|
|
done
|
||
|
|
fi
|
||
|
|
|
||
|
|
- name: Deploy
|
||
|
|
run: |
|
||
|
|
curl -X POST ${{ secrets.PHOENIX_API }}/api/v1/environment/promotions/${{ steps.promote.outputs.promotion_id }}/deploy \
|
||
|
|
-H "Authorization: Bearer ${{ secrets.PHOENIX_TOKEN }}"
|
||
|
|
```
|
||
|
|
|
||
|
|
### GitLab CI: Multi-Stage Promotion
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# .gitlab-ci.yml
|
||
|
|
|
||
|
|
stages:
|
||
|
|
- build
|
||
|
|
- test
|
||
|
|
- promote-dev
|
||
|
|
- promote-int
|
||
|
|
- promote-uat
|
||
|
|
- promote-staging
|
||
|
|
- promote-prod
|
||
|
|
|
||
|
|
variables:
|
||
|
|
PHOENIX_API: "https://api.phoenix.sankofa.nexus"
|
||
|
|
ARTIFACT_REGISTRY: "https://artifacts.phoenix.sankofa.nexus"
|
||
|
|
|
||
|
|
build:
|
||
|
|
stage: build
|
||
|
|
script:
|
||
|
|
- docker build -t $ARTIFACT_REGISTRY/app:$CI_COMMIT_SHA .
|
||
|
|
- docker push $ARTIFACT_REGISTRY/app:$CI_COMMIT_SHA
|
||
|
|
artifacts:
|
||
|
|
paths:
|
||
|
|
- artifact-id.txt
|
||
|
|
|
||
|
|
test:
|
||
|
|
stage: test
|
||
|
|
script:
|
||
|
|
- docker run $ARTIFACT_REGISTRY/app:$CI_COMMIT_SHA npm test
|
||
|
|
- docker run $ARTIFACT_REGISTRY/app:$CI_COMMIT_SHA npm run lint
|
||
|
|
|
||
|
|
promote-to-dev:
|
||
|
|
stage: promote-dev
|
||
|
|
script:
|
||
|
|
- |
|
||
|
|
curl -X POST $PHOENIX_API/api/v1/environment/promotions \
|
||
|
|
-H "Authorization: Bearer $PHOENIX_TOKEN" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d "{
|
||
|
|
\"artifactId\": \"$(cat artifact-id.txt)\",
|
||
|
|
\"fromEnvironmentId\": \"source\",
|
||
|
|
\"toEnvironmentId\": \"dev-env-id\"
|
||
|
|
}"
|
||
|
|
only:
|
||
|
|
- main
|
||
|
|
- develop
|
||
|
|
|
||
|
|
promote-to-prod:
|
||
|
|
stage: promote-prod
|
||
|
|
script:
|
||
|
|
- |
|
||
|
|
# Production requires approval
|
||
|
|
PROMOTION_ID=$(curl -X POST $PHOENIX_API/api/v1/environment/promotions \
|
||
|
|
-H "Authorization: Bearer $PHOENIX_TOKEN" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d "{
|
||
|
|
\"artifactId\": \"$(cat artifact-id.txt)\",
|
||
|
|
\"fromEnvironmentId\": \"staging-env-id\",
|
||
|
|
\"toEnvironmentId\": \"prod-env-id\"
|
||
|
|
}" | jq -r '.id')
|
||
|
|
|
||
|
|
echo "Promotion $PROMOTION_ID requires approval"
|
||
|
|
echo "Waiting for approval..."
|
||
|
|
|
||
|
|
# Wait for approval (with timeout)
|
||
|
|
timeout 3600 bash -c 'until curl -s $PHOENIX_API/api/v1/environment/promotions/$PROMOTION_ID \
|
||
|
|
-H "Authorization: Bearer $PHOENIX_TOKEN" | jq -e ".status == \"APPROVED\""; do sleep 30; done'
|
||
|
|
only:
|
||
|
|
- main
|
||
|
|
when: manual
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Multi-Region Deployment Examples
|
||
|
|
|
||
|
|
### Example: Deploy to Multiple Regions
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Deploy application to multiple regions with data residency
|
||
|
|
|
||
|
|
const regions = ["region-a", "region-b", "region-c"];
|
||
|
|
|
||
|
|
// Create subscriptions per region
|
||
|
|
const subscriptions = await Promise.all(regions.map(region =>
|
||
|
|
phoenix.subscription.createSubscription({
|
||
|
|
name: `subscription-${region}`,
|
||
|
|
tenantId: tenant.id,
|
||
|
|
type: "PRODUCT",
|
||
|
|
serviceBundles: [{
|
||
|
|
service: "COMPUTE",
|
||
|
|
enabled: true,
|
||
|
|
quotas: {
|
||
|
|
vcpu: 50,
|
||
|
|
memory: 256,
|
||
|
|
instances: 25
|
||
|
|
}
|
||
|
|
}],
|
||
|
|
regions: [region]
|
||
|
|
})
|
||
|
|
));
|
||
|
|
|
||
|
|
// Create environments per region
|
||
|
|
const environments = await Promise.all(regions.map((region, index) =>
|
||
|
|
phoenix.environment.createEnvironment({
|
||
|
|
name: `prod-env-${region}`,
|
||
|
|
subscriptionId: subscriptions[index].id,
|
||
|
|
type: "PROD",
|
||
|
|
region: region,
|
||
|
|
dataIsolation: {
|
||
|
|
encryptionAtRest: true,
|
||
|
|
encryptionInTransit: true,
|
||
|
|
dataBoundaries: [{
|
||
|
|
region: region,
|
||
|
|
enforcement: "HARD"
|
||
|
|
}]
|
||
|
|
}
|
||
|
|
})
|
||
|
|
));
|
||
|
|
|
||
|
|
// Deploy application to each region
|
||
|
|
const deployments = await Promise.all(environments.map(env =>
|
||
|
|
phoenix.environment.deploy({
|
||
|
|
environmentId: env.id,
|
||
|
|
artifactId: "artifact-id",
|
||
|
|
metadata: {
|
||
|
|
version: "1.0.0",
|
||
|
|
region: env.region
|
||
|
|
}
|
||
|
|
})
|
||
|
|
));
|
||
|
|
|
||
|
|
console.log("Deployed to regions:", deployments.map(d => d.region));
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Promotion Flow Examples
|
||
|
|
|
||
|
|
### Example: Automated Promotion with Policy Validation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Automated promotion flow with policy validation
|
||
|
|
|
||
|
|
async function promoteArtifact(
|
||
|
|
artifactId: string,
|
||
|
|
fromEnvId: string,
|
||
|
|
toEnvId: string
|
||
|
|
): Promise<PromotionResult> {
|
||
|
|
// Step 1: Validate promotion policy
|
||
|
|
const policyValidation = await phoenix.environment.validatePromotion({
|
||
|
|
fromEnvironmentId: fromEnvId,
|
||
|
|
toEnvironmentId: toEnvId,
|
||
|
|
artifactId: artifactId
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!policyValidation.allowed) {
|
||
|
|
throw new Error(`Promotion not allowed: ${policyValidation.reason}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Step 2: Create promotion request
|
||
|
|
const promotion = await phoenix.environment.promoteArtifact({
|
||
|
|
artifactId: artifactId,
|
||
|
|
fromEnvironmentId: fromEnvId,
|
||
|
|
toEnvironmentId: toEnvId,
|
||
|
|
metadata: {
|
||
|
|
version: "1.2.3",
|
||
|
|
changelog: "Production release"
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Step 3: Check if approval required
|
||
|
|
if (promotion.approvalRequired) {
|
||
|
|
console.log("Approval required. Waiting for approval...");
|
||
|
|
|
||
|
|
// Wait for approval (with timeout)
|
||
|
|
const approval = await waitForApproval(promotion.id, 3600000); // 1 hour timeout
|
||
|
|
|
||
|
|
if (approval.status !== "APPROVED") {
|
||
|
|
throw new Error(`Promotion rejected: ${approval.reason}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Step 4: Deploy
|
||
|
|
const deployment = await phoenix.environment.deploy({
|
||
|
|
environmentId: toEnvId,
|
||
|
|
artifactId: artifactId,
|
||
|
|
promotionId: promotion.id
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
promotionId: promotion.id,
|
||
|
|
deploymentId: deployment.id,
|
||
|
|
status: "SUCCESS"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async function waitForApproval(
|
||
|
|
promotionId: string,
|
||
|
|
timeout: number
|
||
|
|
): Promise<ApprovalStatus> {
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
while (Date.now() - startTime < timeout) {
|
||
|
|
const promotion = await phoenix.environment.getPromotion(promotionId);
|
||
|
|
|
||
|
|
if (promotion.status === "APPROVED" || promotion.status === "REJECTED") {
|
||
|
|
return {
|
||
|
|
status: promotion.status,
|
||
|
|
reason: promotion.reason
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
await sleep(30000); // Check every 30 seconds
|
||
|
|
}
|
||
|
|
|
||
|
|
throw new Error("Approval timeout");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Integration Examples
|
||
|
|
|
||
|
|
### Example: Keycloak Integration
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Automatic Keycloak realm creation for tenant
|
||
|
|
|
||
|
|
async function createTenantWithKeycloak(tenantData: CreateTenantInput) {
|
||
|
|
// Step 1: Create tenant
|
||
|
|
const tenant = await phoenix.tenancy.createTenant(tenantData);
|
||
|
|
|
||
|
|
// Step 2: Create Keycloak realm (automatic via integration)
|
||
|
|
const keycloakRealm = await phoenix.tenancy.syncKeycloakRealm(tenant.id);
|
||
|
|
|
||
|
|
// Step 3: Configure identity provider
|
||
|
|
if (tenantData.identityProvider.type === "AZURE_AD") {
|
||
|
|
await phoenix.tenancy.configureIdentityProvider(tenant.id, {
|
||
|
|
type: "AZURE_AD",
|
||
|
|
config: {
|
||
|
|
tenantId: tenantData.identityProvider.config.tenantId,
|
||
|
|
clientId: tenantData.identityProvider.config.clientId,
|
||
|
|
clientSecret: tenantData.identityProvider.config.clientSecret
|
||
|
|
},
|
||
|
|
ssoEnabled: true,
|
||
|
|
mfaRequired: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
tenant: tenant,
|
||
|
|
keycloakRealm: keycloakRealm
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example: Proxmox Integration
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Provision infrastructure via Proxmox
|
||
|
|
|
||
|
|
async function provisionInfrastructure(
|
||
|
|
environmentId: string,
|
||
|
|
vmSpec: VMSpecification
|
||
|
|
) {
|
||
|
|
const environment = await phoenix.environment.getEnvironment(environmentId);
|
||
|
|
const subscription = await phoenix.subscription.getSubscription(
|
||
|
|
environment.subscriptionId
|
||
|
|
);
|
||
|
|
|
||
|
|
// Check quotas
|
||
|
|
const quotaCheck = await phoenix.subscription.checkQuota(
|
||
|
|
subscription.id,
|
||
|
|
"COMPUTE"
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!quotaCheck.available) {
|
||
|
|
throw new Error(`Quota exceeded: ${quotaCheck.message}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Provision VM via Proxmox
|
||
|
|
const vm = await phoenix.infrastructure.proxmox.createVM({
|
||
|
|
environmentId: environmentId,
|
||
|
|
node: vmSpec.node,
|
||
|
|
name: vmSpec.name,
|
||
|
|
vcpus: vmSpec.vcpus,
|
||
|
|
memory: vmSpec.memory,
|
||
|
|
disk: vmSpec.disk,
|
||
|
|
network: vmSpec.network
|
||
|
|
});
|
||
|
|
|
||
|
|
return vm;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- **[Operating Model](./OPERATING_MODEL.md)** - Complete operating model
|
||
|
|
- **[API Specification](./API_SPECIFICATION.md)** - Complete API reference
|
||
|
|
- **[MVP Control Plane](./MVP_CONTROL_PLANE.md)** - MVP implementation guide
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Last Updated**: 2025-01-09
|
||
|
|
**Version**: 1.0
|
||
|
|
**Status**: Complete Implementation Examples
|
||
|
|
|