forked from diegoecab/oci-deal-accelerator
Accept flat-spec input aliases across generators
Harden bizcase, bom, deck, and diagram generators to tolerate payload shape variants (metadata/cover/summary/line_item aliases, current_state as string, alternate pillar keys) so MCP and CLI flat specs render consistently. Add input-alias tests per generator. Also loosen KB governance tests to handle multi-document service YAMLs with optional changelogs, untrack the customer demo output under examples/output-demo-pharma-mx/ (matches .gitignore), and ship an ADB-S vs Aurora 500GB sample deck. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,40 @@
|
|||||||
"WebFetch(domain:github.com)",
|
"WebFetch(domain:github.com)",
|
||||||
"Bash(curl -s --max-time 15 -H \"X-Oracle-Accept-CurrencyCode: USD\" \"https://itra.oraclecloud.com/itas/.anon/myservices/api/v1/products?limit=2&partNumber=B110627\")",
|
"Bash(curl -s --max-time 15 -H \"X-Oracle-Accept-CurrencyCode: USD\" \"https://itra.oraclecloud.com/itas/.anon/myservices/api/v1/products?limit=2&partNumber=B110627\")",
|
||||||
"Bash(curl -v --max-time 10 -H \"X-Oracle-Accept-CurrencyCode: USD\" \"https://itra.oraclecloud.com/itas/.anon/myservices/api/v1/products?limit=1\")",
|
"Bash(curl -v --max-time 10 -H \"X-Oracle-Accept-CurrencyCode: USD\" \"https://itra.oraclecloud.com/itas/.anon/myservices/api/v1/products?limit=1\")",
|
||||||
"Bash(curl -s --max-time 15 \"https://apexapps.oracle.com/pls/apex/cetools/api/v1/products/?partNumber=B110627¤cyCode=USD\")"
|
"Bash(curl -s --max-time 15 \"https://apexapps.oracle.com/pls/apex/cetools/api/v1/products/?partNumber=B110627¤cyCode=USD\")",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(echo \"exit=$?\")",
|
||||||
|
"Bash(echo \"quiet exit=$?\")",
|
||||||
|
"Bash(make freshness:*)",
|
||||||
|
"Bash(echo \"freshness exit=$?\")",
|
||||||
|
"Bash(make help:*)",
|
||||||
|
"Bash(make lint:*)",
|
||||||
|
"Bash(curl -sS \"https://apexapps.oracle.com/pls/apex/cetools/api/v1/products/?currencyCode=USD\")",
|
||||||
|
"Bash(/bin/rm kb/pricing/ai-ml.yaml kb/pricing/analytics.yaml kb/pricing/containers-serverless.yaml kb/pricing/database.yaml kb/pricing/developer.yaml kb/pricing/hybrid-vmware.yaml kb/pricing/integration.yaml kb/pricing/networking.yaml kb/pricing/observability.yaml kb/pricing/pricing-models.yaml kb/pricing/security.yaml kb/pricing/storage.yaml)",
|
||||||
|
"Bash(python3.12 tools/oci_deck_gen.py --spec examples/proposal-spec.yaml --output examples/output-cli-improvements-deliverables/architecture-proposal.pptx)",
|
||||||
|
"Bash(python3.12 tools/oci_diagram_gen.py --spec examples/diagram-spec.yaml --output examples/output-cli-improvements-deliverables/architecture.drawio)",
|
||||||
|
"Bash(python3.12 tools/oci_pdf_gen.py --spec examples/proposal-spec.yaml --output examples/output-cli-improvements-deliverables/customer-proposal.pdf)",
|
||||||
|
"Bash(python3.12 tools/oci_bom_gen.py --spec examples/exacs-bom-spec.yaml --output examples/output-cli-improvements-deliverables/exacs-bom.xlsx)",
|
||||||
|
"Bash(python3.12 tools/oci_bom_gen.py --spec examples/exacs-x11m-bom-spec.yaml --output examples/output-cli-improvements-deliverables/exacs-x11m-bom.xlsx)",
|
||||||
|
"Bash(python3.12 tools/kb_cli.py search \"Autonomous Database\" --json)",
|
||||||
|
"Bash(python3.12 tools/findings_cli.py search \"adb\" --product ADB-S --json)",
|
||||||
|
"Bash(python3.12 tools/findings_cli.py stats --json)",
|
||||||
|
"Bash(python3.12 tools/kb_cli.py arch-search --services adb-s exacs --tags ha-dr --json)",
|
||||||
|
"Bash(python3.12 tools/kb_cli.py ecal-score --summary \"ADB-S migration with Data Guard business case architecture review customer profile strategy map handover go-live\" --json)",
|
||||||
|
"Bash(python3.12 tools/oci_bom_gen.py --spec examples/exacs-bom-spec.yaml --output examples/output-cli-improvements-deliverables/exacs-bom-appca.xlsx --appca)",
|
||||||
|
"Bash(python3.12 tools/oci_bom_gen.py --spec examples/exacs-x11m-bom-spec.yaml --output examples/output-cli-improvements-deliverables/exacs-x11m-bom-appca.xlsx --appca)",
|
||||||
|
"Bash(python3.12:*)",
|
||||||
|
"Bash(pip3.12 list:*)",
|
||||||
|
"Bash(pip3.12 install:*)",
|
||||||
|
"Bash(xargs ls:*)",
|
||||||
|
"WebFetch(domain:www.drawio.com)",
|
||||||
|
"WebFetch(domain:jgraph.github.io)",
|
||||||
|
"WebFetch(domain:raw.githubusercontent.com)",
|
||||||
|
"Bash(ls /root/.claude/projects/-mnt-c-Users-Diego-Documents-oci-deal-accelerator-oracle-oci-deal-accelerator/a0d00b4c-8d61-421c-8ba9-19bb85f15fdb/tool-results/*.zip)",
|
||||||
|
"WebFetch(domain:developers.openai.com)",
|
||||||
|
"Bash(echo \"---exit:$?---\")",
|
||||||
|
"Bash(date +%s)",
|
||||||
|
"Read(//tmp/**)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
# OCI Deal Accelerator — BOM Spec
|
|
||||||
# Demo: PharmaCorp Mexico — ExaCS X11M + networking + security
|
|
||||||
# SKUs verified against kb/pricing/oci-sku-catalog.yaml
|
|
||||||
|
|
||||||
metadata:
|
|
||||||
customer: "PharmaCorp Mexico"
|
|
||||||
prepared_by: "Carlos Mendoza — Solutions Architect"
|
|
||||||
date: "2026-04-12"
|
|
||||||
currency: "USD"
|
|
||||||
contract_months: 12
|
|
||||||
discount_pct: 0.45
|
|
||||||
notes: "ExaCS BYOL — customer has existing Oracle DB EE licenses with active support"
|
|
||||||
|
|
||||||
line_items:
|
|
||||||
# ExaCS X11M — DB Servers (Queretaro Production)
|
|
||||||
- category: "Exadata Cloud Infrastructure"
|
|
||||||
product: "ExaCS X11M DB Server"
|
|
||||||
sku: "B110627"
|
|
||||||
qty: 2
|
|
||||||
unit: "Hosted Environment Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "2 DB servers for Quarter Rack — Queretaro (Prod)"
|
|
||||||
|
|
||||||
# ExaCS X11M — Storage Servers (Queretaro Production)
|
|
||||||
- category: "Exadata Cloud Infrastructure"
|
|
||||||
product: "ExaCS X11M Storage Server"
|
|
||||||
sku: "B110629"
|
|
||||||
qty: 3
|
|
||||||
unit: "Hosted Environment Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "3 storage servers — Quarter Rack minimum"
|
|
||||||
|
|
||||||
# ExaCS X11M — ECPU BYOL (Queretaro)
|
|
||||||
- category: "Exadata Cloud Infrastructure"
|
|
||||||
product: "ExaCS X11M ECPU BYOL"
|
|
||||||
sku: "B110632"
|
|
||||||
qty: 128
|
|
||||||
unit: "ECPU Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "128 ECPUs BYOL — EBS + SAP + DW"
|
|
||||||
|
|
||||||
# ExaCS X11M — DB Servers (Sao Paulo DR Standby)
|
|
||||||
- category: "Exadata Cloud Infrastructure"
|
|
||||||
product: "ExaCS X11M DB Server (DR)"
|
|
||||||
sku: "B110627"
|
|
||||||
qty: 2
|
|
||||||
unit: "Hosted Environment Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "2 DB servers — Sao Paulo (DR Standby)"
|
|
||||||
|
|
||||||
# ExaCS X11M — Storage Servers (Sao Paulo DR)
|
|
||||||
- category: "Exadata Cloud Infrastructure"
|
|
||||||
product: "ExaCS X11M Storage Server (DR)"
|
|
||||||
sku: "B110629"
|
|
||||||
qty: 3
|
|
||||||
unit: "Hosted Environment Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "3 storage servers — DR Standby"
|
|
||||||
|
|
||||||
# ExaCS X11M — ECPU BYOL (Sao Paulo DR)
|
|
||||||
- category: "Exadata Cloud Infrastructure"
|
|
||||||
product: "ExaCS X11M ECPU BYOL (DR)"
|
|
||||||
sku: "B110632"
|
|
||||||
qty: 128
|
|
||||||
unit: "ECPU Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "128 ECPUs BYOL — Data Guard standby"
|
|
||||||
|
|
||||||
# ADB-S for DW migration (Phase 2)
|
|
||||||
- category: "Autonomous Database"
|
|
||||||
product: "ADB-S ECPU BYOL"
|
|
||||||
sku: "B95703"
|
|
||||||
qty: 16
|
|
||||||
unit: "ECPU Per Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "16 ECPU for DW migration to ADB-S (optional Phase 2)"
|
|
||||||
|
|
||||||
# ADB-S Storage
|
|
||||||
- category: "Autonomous Database"
|
|
||||||
product: "ADB-S Storage (ATP)"
|
|
||||||
sku: "B95706"
|
|
||||||
qty: 2000
|
|
||||||
unit: "Gigabyte Storage Capacity Per Month"
|
|
||||||
hours_units: 1
|
|
||||||
months: 12
|
|
||||||
notes: "2TB storage for DW"
|
|
||||||
|
|
||||||
# FastConnect 10Gbps Dual
|
|
||||||
- category: "Networking"
|
|
||||||
product: "FastConnect 10 Gbps (Primary)"
|
|
||||||
sku: "B88326"
|
|
||||||
qty: 1
|
|
||||||
unit: "Port Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "Primary circuit — Mexico City to Queretaro"
|
|
||||||
|
|
||||||
- category: "Networking"
|
|
||||||
product: "FastConnect 10 Gbps (Redundant)"
|
|
||||||
sku: "B88326"
|
|
||||||
qty: 1
|
|
||||||
unit: "Port Hour"
|
|
||||||
hours_units: 744
|
|
||||||
months: 12
|
|
||||||
notes: "Redundant circuit — Mexico City to Queretaro"
|
|
||||||
|
|
||||||
# Object Storage for RMAN backups
|
|
||||||
- category: "Storage"
|
|
||||||
product: "Object Storage"
|
|
||||||
sku: "B91628"
|
|
||||||
qty: 50000
|
|
||||||
unit: "Gigabyte Storage Capacity Per Month"
|
|
||||||
hours_units: 1
|
|
||||||
months: 12
|
|
||||||
notes: "50TB for RMAN backups (DW + EBS)"
|
|
||||||
|
|
||||||
# Block Volume
|
|
||||||
- category: "Storage"
|
|
||||||
product: "Block Volume Storage"
|
|
||||||
sku: "B91961"
|
|
||||||
qty: 10000
|
|
||||||
unit: "Gigabyte Storage Capacity Per Month"
|
|
||||||
hours_units: 1
|
|
||||||
months: 12
|
|
||||||
notes: "10TB additional block storage"
|
|
||||||
|
|
||||||
# OCI Vault
|
|
||||||
- category: "Security"
|
|
||||||
product: "OCI Vault Key Versions"
|
|
||||||
sku: "B92092"
|
|
||||||
qty: 10
|
|
||||||
unit: "Key Version Per Month"
|
|
||||||
hours_units: 1
|
|
||||||
months: 12
|
|
||||||
notes: "TDE keys for 3 databases + DR + backups"
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
# OCI Deal Accelerator — Business Case
|
|
||||||
# Demo: PharmaCorp Mexico — ExaCS migration from on-prem Exadata X6
|
|
||||||
# Follows exact schema expected by oci_bizcase_gen.py
|
|
||||||
|
|
||||||
business_case:
|
|
||||||
customer_name: "PharmaCorp Mexico"
|
|
||||||
prepared_by: "Carlos Mendoza — Solutions Architect"
|
|
||||||
date: "2026-04-12"
|
|
||||||
|
|
||||||
executive_summary: >
|
|
||||||
PharmaCorp Mexico operates 3 mission-critical Oracle databases (EBS R12, SAP, DW)
|
|
||||||
on aging Exadata X6 hardware. The hardware refresh quote is $1.8M and locks the
|
|
||||||
company into 5 more years of on-prem operations with no DR capability. Migration
|
|
||||||
to OCI ExaCS X11M reduces annual infrastructure costs by 61% ($1.75M over 3 years),
|
|
||||||
adds cross-region DR (RPO<15min, RTO<1h), and resolves the pending SOX audit
|
|
||||||
finding on automated patching.
|
|
||||||
|
|
||||||
drivers:
|
|
||||||
primary: "hardware_refresh_avoidance"
|
|
||||||
urgency: "HW refresh due in 6 months — must decide now or commit $1.8M"
|
|
||||||
cost_of_inaction:
|
|
||||||
financial: "$2.88M over 3yr: $1.8M HW refresh + $300K/yr support + $80K/yr DC costs"
|
|
||||||
operational: "No DR today — single site, single point of failure for EBS production"
|
|
||||||
strategic: "SOX audit finding pending: manual quarterly patching is non-compliant"
|
|
||||||
|
|
||||||
tco:
|
|
||||||
horizon_years: 3
|
|
||||||
current_state:
|
|
||||||
annual_infrastructure: 600000
|
|
||||||
annual_licensing: 220000
|
|
||||||
annual_operations: 60000
|
|
||||||
annual_downtime_cost: 50000
|
|
||||||
annual_compliance_cost: 30000
|
|
||||||
breakdown:
|
|
||||||
- item: "Exadata X6 depreciation + refresh amortization"
|
|
||||||
annual: 600000
|
|
||||||
- item: "Oracle DB EE Support & Maintenance"
|
|
||||||
annual: 220000
|
|
||||||
- item: "DBA patching + infra management (part-time)"
|
|
||||||
annual: 60000
|
|
||||||
- item: "Data center (power, cooling, space)"
|
|
||||||
annual: 80000
|
|
||||||
|
|
||||||
proposed_oci:
|
|
||||||
migration_one_time: 50000
|
|
||||||
annual_cloud_consumption: 278400
|
|
||||||
annual_licensing: 0
|
|
||||||
annual_operations: 20000
|
|
||||||
annual_downtime_cost: 0
|
|
||||||
annual_compliance_cost: 0
|
|
||||||
breakdown:
|
|
||||||
- item: "ExaCS X11M Queretaro (Prod, BYOL)"
|
|
||||||
annual: 98400
|
|
||||||
- item: "ExaCS X11M Sao Paulo (DR Standby, BYOL)"
|
|
||||||
annual: 98400
|
|
||||||
- item: "FastConnect 10Gbps Dual"
|
|
||||||
annual: 36720
|
|
||||||
- item: "OCI Vault + Monitoring + Storage"
|
|
||||||
annual: 30000
|
|
||||||
- item: "Reduced DBA operations (automated patching)"
|
|
||||||
annual: 20000
|
|
||||||
- item: "Migration project (one-time, amortized Y1)"
|
|
||||||
annual: 50000
|
|
||||||
|
|
||||||
savings:
|
|
||||||
annual: 661600
|
|
||||||
three_year: 1749600
|
|
||||||
pct: 61
|
|
||||||
|
|
||||||
roi:
|
|
||||||
three_year_roi_pct: 3400
|
|
||||||
payback_months: 1
|
|
||||||
total_investment: 50000
|
|
||||||
annual_net_benefit: 661600
|
|
||||||
notes: >
|
|
||||||
ROI based on 3-year TCO comparison. 1-month payback driven by immediate
|
|
||||||
elimination of HW refresh commitment ($1.8M avoided). Conservative — does
|
|
||||||
not include productivity gains from automated patching or DR risk reduction.
|
|
||||||
|
|
||||||
value_drivers:
|
|
||||||
- category: "Cost Avoidance"
|
|
||||||
driver: "Avoid $1.8M hardware refresh + eliminate DC costs"
|
|
||||||
quantification: "$1.75M saved over 3 years (61% reduction)"
|
|
||||||
|
|
||||||
- category: "Risk Reduction"
|
|
||||||
driver: "Cross-region DR where none exists today"
|
|
||||||
quantification: "RPO<15min, RTO<1h with Data Guard + FSDR"
|
|
||||||
|
|
||||||
- category: "Compliance"
|
|
||||||
driver: "SOX audit finding resolved with automated patching"
|
|
||||||
quantification: "ExaCS auto-patching + OCI Audit + Vault key management"
|
|
||||||
|
|
||||||
- category: "Operational Efficiency"
|
|
||||||
driver: "DBA freed from manual patching + infrastructure management"
|
|
||||||
quantification: "60% reduction in DBA time allocated to patching"
|
|
||||||
|
|
||||||
risks:
|
|
||||||
migration_risks:
|
|
||||||
- risk: "EBS R12 customizations may require OS-level changes post-migration"
|
|
||||||
probability: "Medium"
|
|
||||||
impact: "Medium"
|
|
||||||
mitigation: "Inventory all customizations pre-migration; test in Pre-Prod"
|
|
||||||
|
|
||||||
- risk: "FastConnect provisioning delay (partner-dependent)"
|
|
||||||
probability: "Medium"
|
|
||||||
impact: "Low"
|
|
||||||
mitigation: "Start in Phase 1 week 1; IPSec VPN as interim backup"
|
|
||||||
|
|
||||||
- risk: "HW refresh deadline pressure (6 months)"
|
|
||||||
probability: "High"
|
|
||||||
impact: "High"
|
|
||||||
mitigation: "16-week migration plan fits within window with margin"
|
|
||||||
|
|
||||||
do_nothing_risks:
|
|
||||||
- risk: "No DR — single site, single point of failure for EBS production"
|
|
||||||
probability: "High"
|
|
||||||
impact: "Critical"
|
|
||||||
mitigation: "Cannot be mitigated without infrastructure investment"
|
|
||||||
|
|
||||||
- risk: "SOX audit finding remains unresolved (manual patching)"
|
|
||||||
probability: "High"
|
|
||||||
impact: "High"
|
|
||||||
mitigation: "Remediation requires automated patching — on-prem can't deliver"
|
|
||||||
|
|
||||||
- risk: "$1.8M HW refresh locks in 5 more years of on-prem with no cloud ROI"
|
|
||||||
probability: "High"
|
|
||||||
impact: "High"
|
|
||||||
mitigation: "Sunk cost — delays any cloud migration benefit for 5+ years"
|
|
||||||
|
|
||||||
recommendation:
|
|
||||||
summary: >
|
|
||||||
Proceed with OCI ExaCS migration. 61% cost reduction over 3 years ($1.75M saved),
|
|
||||||
cross-region DR capability, and SOX compliance resolution. The alternative
|
|
||||||
(HW refresh at $1.8M) costs more and leaves DR + compliance gaps unresolved.
|
|
||||||
next_steps:
|
|
||||||
- "Board approval with TCO presentation — May 2026"
|
|
||||||
- "Provision OCI tenancy + ExaCS in Queretaro — June 2026"
|
|
||||||
- "Start FastConnect provisioning with partner — June 2026"
|
|
||||||
- "EBS customization inventory and Pre-Prod testing — June 2026"
|
|
||||||
- "Production cutover — September 2026"
|
|
||||||
|
|
||||||
roadmap:
|
|
||||||
total_duration: "16 weeks (May — September 2026)"
|
|
||||||
phases:
|
|
||||||
- name: "Board Approval"
|
|
||||||
duration: "2 weeks"
|
|
||||||
investment: 0
|
|
||||||
description: "Present TCO + risk analysis to board"
|
|
||||||
|
|
||||||
- name: "Foundation (networking + ExaCS)"
|
|
||||||
duration: "4 weeks"
|
|
||||||
investment: 15000
|
|
||||||
description: "Provision OCI tenancy, ExaCS, FastConnect"
|
|
||||||
|
|
||||||
- name: "EBS Migration + Data Guard"
|
|
||||||
duration: "6 weeks"
|
|
||||||
investment: 25000
|
|
||||||
description: "Migrate EBS R12 with ZDM, configure Data Guard to Sao Paulo"
|
|
||||||
|
|
||||||
- name: "SAP + DW Migration"
|
|
||||||
duration: "4 weeks"
|
|
||||||
investment: 10000
|
|
||||||
description: "Migrate remaining databases, RMAN backup for DW"
|
|
||||||
|
|
||||||
- name: "Cutover + Decommission"
|
|
||||||
duration: "2 weeks"
|
|
||||||
investment: 0
|
|
||||||
description: "Production cutover, decommission Exadata X6"
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# OCI Deal Accelerator — Architecture Diagram Spec
|
|
||||||
# Demo: PharmaCorp Mexico — ExaCS dual-region with Data Guard
|
|
||||||
# Prompt: "ExaCS en Queretaro (prod) + Standby con Data Guard en Sao Paulo,
|
|
||||||
# Hub-Spoke networking con DRG, FastConnect 10Gbps dual desde Mexico City,
|
|
||||||
# bastion en public subnet, WAF para apps web, Vault para TDE keys."
|
|
||||||
#
|
|
||||||
# REQUESTED by user: ExaCS (prod+DR), DRG, FastConnect dual, Bastion, WAF, Vault
|
|
||||||
# TECHNICAL DEPENDENCIES (whitelist): SGW (ExaCS backup), IGW (public subnet)
|
|
||||||
# NOT included: EBS, Monitoring, Logging, Events, FSDR, Object Storage (not requested)
|
|
||||||
#
|
|
||||||
# Container sizes: omitted to let auto-sizing calculate from content.
|
|
||||||
# DRG: listed in gateways — generator places it OUTSIDE VCN, INSIDE region.
|
|
||||||
|
|
||||||
title: "PharmaCorp Mexico — ExaCS Migration Architecture"
|
|
||||||
|
|
||||||
# External: Mexico City DC (left side)
|
|
||||||
external:
|
|
||||||
- id: "mexico_dc"
|
|
||||||
label: "Mexico City\nData Center"
|
|
||||||
icon: "user"
|
|
||||||
x: 30
|
|
||||||
y: 250
|
|
||||||
w: 60
|
|
||||||
h: 70
|
|
||||||
|
|
||||||
# OCI Tenancy
|
|
||||||
tenancy:
|
|
||||||
label: "Oracle Cloud Infrastructure"
|
|
||||||
x: 200
|
|
||||||
y: 60
|
|
||||||
|
|
||||||
regions:
|
|
||||||
# ── Primary Region: Queretaro ──
|
|
||||||
- id: "region_qro"
|
|
||||||
label: "Region — Queretaro (Primary)"
|
|
||||||
primary: true
|
|
||||||
|
|
||||||
vcns:
|
|
||||||
- id: "vcn_hub"
|
|
||||||
label: "Hub VCN (10.0.0.0/16)"
|
|
||||||
|
|
||||||
subnets:
|
|
||||||
# Public subnet — WAF + Bastion (both requested)
|
|
||||||
- id: "subnet_pub"
|
|
||||||
label: "Public Subnet — 10.0.0.0/24"
|
|
||||||
h: 120
|
|
||||||
services:
|
|
||||||
- id: "waf"
|
|
||||||
label: "WAF\n(Web Apps)"
|
|
||||||
type: "waf"
|
|
||||||
w: 120
|
|
||||||
h: 70
|
|
||||||
- id: "bastion"
|
|
||||||
label: "Bastion\nService"
|
|
||||||
type: "bastion"
|
|
||||||
w: 120
|
|
||||||
h: 70
|
|
||||||
|
|
||||||
# DB subnet — ExaCS + Vault (both requested)
|
|
||||||
- id: "subnet_db"
|
|
||||||
label: "Database Subnet (Private) — 10.0.1.0/24"
|
|
||||||
h: 130
|
|
||||||
services:
|
|
||||||
- id: "exacs_prod"
|
|
||||||
label: "ExaCS X11M\nQuarter Rack\n(Production)"
|
|
||||||
type: "exacs"
|
|
||||||
w: 200
|
|
||||||
h: 80
|
|
||||||
- id: "vault"
|
|
||||||
label: "OCI Vault\nTDE Keys"
|
|
||||||
type: "vault"
|
|
||||||
w: 140
|
|
||||||
h: 80
|
|
||||||
|
|
||||||
gateways:
|
|
||||||
# DRG — generator places this OUTSIDE VCN, INSIDE region
|
|
||||||
- id: "drg"
|
|
||||||
label: "DRG"
|
|
||||||
type: "drg"
|
|
||||||
# IGW — whitelist: public subnet requires it
|
|
||||||
- id: "igw"
|
|
||||||
label: "Internet\nGateway"
|
|
||||||
type: "igw"
|
|
||||||
# SGW — whitelist: ExaCS backup requires it
|
|
||||||
- id: "sgw"
|
|
||||||
label: "Service\nGateway"
|
|
||||||
type: "service_gateway"
|
|
||||||
|
|
||||||
# ── DR Region: Sao Paulo ──
|
|
||||||
- id: "region_gru"
|
|
||||||
label: "Region — São Paulo (DR)"
|
|
||||||
|
|
||||||
vcns:
|
|
||||||
- id: "vcn_dr"
|
|
||||||
label: "DR VCN (10.1.0.0/16)"
|
|
||||||
|
|
||||||
subnets:
|
|
||||||
- id: "subnet_dr_db"
|
|
||||||
label: "DB Subnet (Private) — 10.1.1.0/24"
|
|
||||||
h: 130
|
|
||||||
services:
|
|
||||||
- id: "exacs_dr"
|
|
||||||
label: "ExaCS Standby\n(Data Guard)"
|
|
||||||
type: "exacs"
|
|
||||||
w: 200
|
|
||||||
h: 80
|
|
||||||
|
|
||||||
# Connections
|
|
||||||
connections:
|
|
||||||
# FastConnect dual from Mexico City → DRG (will be merged into single edge)
|
|
||||||
- id: "conn_fc1"
|
|
||||||
label: "FastConnect 10Gbps\n(Dual Redundant)"
|
|
||||||
type: "fastconnect"
|
|
||||||
from: "mexico_dc"
|
|
||||||
to: "drg"
|
|
||||||
|
|
||||||
# Vault → ExaCS (TDE keys)
|
|
||||||
- id: "conn_vault"
|
|
||||||
label: "TDE Keys"
|
|
||||||
type: "internal"
|
|
||||||
from: "vault"
|
|
||||||
to: "exacs_prod"
|
|
||||||
|
|
||||||
# Data Guard: Prod → DR
|
|
||||||
- id: "conn_dg"
|
|
||||||
label: "Data Guard\n(Redo Shipping)"
|
|
||||||
type: "data"
|
|
||||||
from: "exacs_prod"
|
|
||||||
to: "exacs_dr"
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
{
|
|
||||||
"cost": {
|
|
||||||
"line_items": [
|
|
||||||
{
|
|
||||||
"component": "ExaCS X11M Quarter Rack \u2014 Queretaro (Prod)",
|
|
||||||
"monthly_payg": "$18,500",
|
|
||||||
"monthly_byol": "$8,200",
|
|
||||||
"notes": "2 DB servers, 3 storage servers, BYOL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "ExaCS X11M Quarter Rack \u2014 Sao Paulo (DR)",
|
|
||||||
"monthly_payg": "$18,500",
|
|
||||||
"monthly_byol": "$8,200",
|
|
||||||
"notes": "ADG standby, BYOL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "ADB-S 16 ECPU (DW alternative \u2014 future)",
|
|
||||||
"monthly_payg": "$2,400",
|
|
||||||
"monthly_byol": "$1,200",
|
|
||||||
"notes": "Optional \u2014 migrate DW to ADB-S in Phase 2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "FastConnect 10Gbps Dual",
|
|
||||||
"monthly_payg": "$3,060",
|
|
||||||
"notes": "2 x $1,530/month"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "OCI Vault",
|
|
||||||
"monthly_payg": "$500",
|
|
||||||
"notes": "HSM-backed key management"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "Object Storage (50TB backup)",
|
|
||||||
"monthly_payg": "$1,200",
|
|
||||||
"notes": "RMAN backups + DW backup"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "WAF + Logging + Monitoring",
|
|
||||||
"monthly_payg": "$800",
|
|
||||||
"notes": "WAF for EBS web tier + audit logging"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "TOTAL",
|
|
||||||
"monthly_payg": "$45,000",
|
|
||||||
"monthly_byol": "$23,200",
|
|
||||||
"notes": "BYOL saves 48%"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"assumptions": [
|
|
||||||
"BYOL assumes existing Oracle Database EE licenses with active support",
|
|
||||||
"ExaCS pricing based on current OCI list prices",
|
|
||||||
"DR standby at full rate (ADG requires active compute)",
|
|
||||||
"No middleware licensing included (EBS app tier on compute VMs)",
|
|
||||||
"FastConnect partner charges not included"
|
|
||||||
],
|
|
||||||
"show_byol": true
|
|
||||||
},
|
|
||||||
"cost_comparison": {
|
|
||||||
"title": "Cost Comparison: On-Prem HW Refresh vs OCI Migration",
|
|
||||||
"col_headers": [
|
|
||||||
"Component",
|
|
||||||
"On-Prem Refresh (5yr)",
|
|
||||||
"OCI ExaCS BYOL",
|
|
||||||
"Delta"
|
|
||||||
],
|
|
||||||
"rows": [
|
|
||||||
{
|
|
||||||
"item": "Infrastructure (hardware/cloud)",
|
|
||||||
"current": "$1,800,000 upfront",
|
|
||||||
"oci": "$278,400/yr",
|
|
||||||
"savings": "Break-even at 3.2 years"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "Annual Support & Maintenance",
|
|
||||||
"current": "$220,000/yr",
|
|
||||||
"oci": "Included in OCI",
|
|
||||||
"savings": "-100%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "DR Infrastructure",
|
|
||||||
"current": "$0 (no DR today)",
|
|
||||||
"oci": "$98,400/yr",
|
|
||||||
"savings": "New capability \u2014 no DR today"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "Data Center (power/cooling/space)",
|
|
||||||
"current": "$80,000/yr",
|
|
||||||
"oci": "$0",
|
|
||||||
"savings": "-100%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "DBA Operations (patching)",
|
|
||||||
"current": "$60,000/yr (part-time DBA)",
|
|
||||||
"oci": "$0 (automated)",
|
|
||||||
"savings": "-100%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "TOTAL ANNUAL (year 1)",
|
|
||||||
"current": "$2,160,000",
|
|
||||||
"oci": "$376,800",
|
|
||||||
"savings": "-83%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"item": "TOTAL 3-YEAR TCO",
|
|
||||||
"current": "$2,880,000",
|
|
||||||
"oci": "$1,130,400",
|
|
||||||
"savings": "-$1.75M (61%)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,348 +0,0 @@
|
|||||||
well_architected_scorecard:
|
|
||||||
overall_status: GAPS_IDENTIFIED
|
|
||||||
generated_date: '2026-04-12T23:15:18.437115'
|
|
||||||
architecture_name: /tmp/demo-flawed-arch.yaml
|
|
||||||
customer: Demo Flawed Arch
|
|
||||||
summary:
|
|
||||||
total_checks: 23
|
|
||||||
total_passed: 1
|
|
||||||
total_gaps: 22
|
|
||||||
high_severity_gaps: 10
|
|
||||||
medium_severity_gaps: 10
|
|
||||||
low_severity_gaps: 2
|
|
||||||
pillars:
|
|
||||||
security_compliance:
|
|
||||||
status: GAPS_IDENTIFIED
|
|
||||||
checks_passed: 0
|
|
||||||
checks_total: 15
|
|
||||||
categories:
|
|
||||||
identity_access:
|
|
||||||
passed: 0
|
|
||||||
total: 4
|
|
||||||
gaps:
|
|
||||||
- check_id: SEC-IAM-001
|
|
||||||
area: Identity & Access
|
|
||||||
finding: no IAM policy strategy defined or broad permissions used
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: architecture specifies granular IAM policies per compartment/service
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-IAM-002
|
|
||||||
area: Identity & Access
|
|
||||||
finding: no MFA mentioned in identity setup
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: MFA is specified in identity configuration
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-IAM-003
|
|
||||||
area: Identity & Access
|
|
||||||
finding: no mention of admin account restrictions
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: separate admin groups defined for operations
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-IAM-004
|
|
||||||
area: Identity & Access
|
|
||||||
finding: stored credentials or API keys used for service accounts
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: instance principals or resource principals specified for
|
|
||||||
service-to-service auth
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
resource_isolation:
|
|
||||||
passed: 0
|
|
||||||
total: 3
|
|
||||||
gaps:
|
|
||||||
- check_id: SEC-ISO-001
|
|
||||||
area: Resource Isolation
|
|
||||||
finding: flat compartment structure or no compartment strategy
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: hierarchical compartment structure defined
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-ISO-002
|
|
||||||
area: Resource Isolation
|
|
||||||
finding: no tagging strategy defined
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: defined tag namespaces with cost, environment, and owner
|
|
||||||
tags
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-ISO-004
|
|
||||||
area: Resource Isolation
|
|
||||||
finding: user credentials used for cross-resource access
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: resource principals used for cross-service access
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
database_security:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
data_protection:
|
|
||||||
passed: 0
|
|
||||||
total: 2
|
|
||||||
gaps:
|
|
||||||
- check_id: SEC-DAT-001
|
|
||||||
area: Data Protection
|
|
||||||
finding: any storage service without encryption at rest
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: encryption at rest specified for all storage services
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-DAT-002
|
|
||||||
area: Data Protection
|
|
||||||
finding: using Oracle-managed keys or no key management strategy
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: OCI Vault specified for key management
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
network_security:
|
|
||||||
passed: 0
|
|
||||||
total: 3
|
|
||||||
gaps:
|
|
||||||
- check_id: SEC-NET-001
|
|
||||||
area: Network Security
|
|
||||||
finding: only Security Lists used without NSGs
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: NSGs used as primary network security mechanism
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-NET-002
|
|
||||||
area: Network Security
|
|
||||||
finding: SSH open to 0.0.0.0/0
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: SSH access restricted to specific CIDR ranges or bastion
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-NET-004
|
|
||||||
area: Network Security
|
|
||||||
finding: OCI services accessed via internet gateway
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: Service Gateway configured in VCN
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
monitoring_audit:
|
|
||||||
passed: 0
|
|
||||||
total: 3
|
|
||||||
gaps:
|
|
||||||
- check_id: SEC-MON-001
|
|
||||||
area: Monitoring & Audit
|
|
||||||
finding: Cloud Guard not included in architecture
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: Cloud Guard configured with appropriate recipes
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-MON-002
|
|
||||||
area: Monitoring & Audit
|
|
||||||
finding: audit service not mentioned or retention not configured
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: Audit service enabled (enabled by default, verify retention)
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
- check_id: SEC-MON-003
|
|
||||||
area: Monitoring & Audit
|
|
||||||
finding: no VCN Flow Logs configured
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: VCN Flow Logs enabled for critical subnets
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
reliability_resilience:
|
|
||||||
status: PASS_WITH_RECOMMENDATIONS
|
|
||||||
checks_passed: 0
|
|
||||||
checks_total: 1
|
|
||||||
categories:
|
|
||||||
scalability:
|
|
||||||
passed: 0
|
|
||||||
total: 1
|
|
||||||
gaps:
|
|
||||||
- check_id: REL-SCL-002
|
|
||||||
area: Scalability
|
|
||||||
finding: no service limit review planned
|
|
||||||
severity: LOW
|
|
||||||
recommendation: service limit review mentioned in deployment plan
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html
|
|
||||||
fault_tolerant_networking:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
data_backup:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
data_replication:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
disaster_recovery:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
performance_cost:
|
|
||||||
status: PASS_WITH_RECOMMENDATIONS
|
|
||||||
checks_passed: 0
|
|
||||||
checks_total: 2
|
|
||||||
categories:
|
|
||||||
compute_sizing:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
storage_strategy:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
network_tuning:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
cost_management:
|
|
||||||
passed: 0
|
|
||||||
total: 2
|
|
||||||
gaps:
|
|
||||||
- check_id: PERF-CST-003
|
|
||||||
area: Cost Management
|
|
||||||
finding: no budget alerts configured
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: OCI Budgets configured with alert thresholds
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/index.html
|
|
||||||
- check_id: PERF-CST-004
|
|
||||||
area: Cost Management
|
|
||||||
finding: no cost attribution tagging strategy
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: cost-tracking tags defined and enforced
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/index.html
|
|
||||||
operational_efficiency:
|
|
||||||
status: GAPS_IDENTIFIED
|
|
||||||
checks_passed: 1
|
|
||||||
checks_total: 5
|
|
||||||
categories:
|
|
||||||
deployment_strategy:
|
|
||||||
passed: 0
|
|
||||||
total: 1
|
|
||||||
gaps:
|
|
||||||
- check_id: OPS-DEP-001
|
|
||||||
area: Deployment Strategy
|
|
||||||
finding: no IaC strategy specified
|
|
||||||
severity: HIGH
|
|
||||||
recommendation: Terraform or Resource Manager specified for infrastructure
|
|
||||||
provisioning
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html
|
|
||||||
workload_monitoring:
|
|
||||||
passed: 1
|
|
||||||
total: 2
|
|
||||||
gaps:
|
|
||||||
- check_id: OPS-MON-003
|
|
||||||
area: Workload Monitoring
|
|
||||||
finding: no centralized log analysis solution
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: OCI Logging Analytics configured
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html
|
|
||||||
os_management:
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
gaps: []
|
|
||||||
operations_support:
|
|
||||||
passed: 0
|
|
||||||
total: 2
|
|
||||||
gaps:
|
|
||||||
- check_id: OPS-SUP-001
|
|
||||||
area: Operations Support
|
|
||||||
finding: no support plan consideration
|
|
||||||
severity: LOW
|
|
||||||
recommendation: Oracle support plan level specified and matches criticality
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html
|
|
||||||
- check_id: OPS-SUP-002
|
|
||||||
area: Operations Support
|
|
||||||
finding: no notification configuration
|
|
||||||
severity: MEDIUM
|
|
||||||
recommendation: OCI Notifications configured for operational events
|
|
||||||
wa_reference: https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html
|
|
||||||
distributed_cloud:
|
|
||||||
status: NOT_APPLICABLE
|
|
||||||
checks_passed: 0
|
|
||||||
checks_total: 0
|
|
||||||
categories: {}
|
|
||||||
action_items:
|
|
||||||
high_priority:
|
|
||||||
- check_id: SEC-IAM-001
|
|
||||||
pillar: Identity & Access
|
|
||||||
finding: no IAM policy strategy defined or broad permissions used
|
|
||||||
recommendation: architecture specifies granular IAM policies per compartment/service
|
|
||||||
- check_id: SEC-IAM-002
|
|
||||||
pillar: Identity & Access
|
|
||||||
finding: no MFA mentioned in identity setup
|
|
||||||
recommendation: MFA is specified in identity configuration
|
|
||||||
- check_id: SEC-IAM-003
|
|
||||||
pillar: Identity & Access
|
|
||||||
finding: no mention of admin account restrictions
|
|
||||||
recommendation: separate admin groups defined for operations
|
|
||||||
- check_id: SEC-IAM-004
|
|
||||||
pillar: Identity & Access
|
|
||||||
finding: stored credentials or API keys used for service accounts
|
|
||||||
recommendation: instance principals or resource principals specified for service-to-service
|
|
||||||
auth
|
|
||||||
- check_id: SEC-ISO-001
|
|
||||||
pillar: Resource Isolation
|
|
||||||
finding: flat compartment structure or no compartment strategy
|
|
||||||
recommendation: hierarchical compartment structure defined
|
|
||||||
- check_id: SEC-ISO-004
|
|
||||||
pillar: Resource Isolation
|
|
||||||
finding: user credentials used for cross-resource access
|
|
||||||
recommendation: resource principals used for cross-service access
|
|
||||||
- check_id: SEC-DAT-001
|
|
||||||
pillar: Data Protection
|
|
||||||
finding: any storage service without encryption at rest
|
|
||||||
recommendation: encryption at rest specified for all storage services
|
|
||||||
- check_id: SEC-NET-002
|
|
||||||
pillar: Network Security
|
|
||||||
finding: SSH open to 0.0.0.0/0
|
|
||||||
recommendation: SSH access restricted to specific CIDR ranges or bastion
|
|
||||||
- check_id: SEC-MON-001
|
|
||||||
pillar: Monitoring & Audit
|
|
||||||
finding: Cloud Guard not included in architecture
|
|
||||||
recommendation: Cloud Guard configured with appropriate recipes
|
|
||||||
- check_id: OPS-DEP-001
|
|
||||||
pillar: Deployment Strategy
|
|
||||||
finding: no IaC strategy specified
|
|
||||||
recommendation: Terraform or Resource Manager specified for infrastructure provisioning
|
|
||||||
medium_priority:
|
|
||||||
- check_id: SEC-ISO-002
|
|
||||||
pillar: Resource Isolation
|
|
||||||
finding: no tagging strategy defined
|
|
||||||
recommendation: defined tag namespaces with cost, environment, and owner tags
|
|
||||||
- check_id: SEC-DAT-002
|
|
||||||
pillar: Data Protection
|
|
||||||
finding: using Oracle-managed keys or no key management strategy
|
|
||||||
recommendation: OCI Vault specified for key management
|
|
||||||
- check_id: SEC-NET-001
|
|
||||||
pillar: Network Security
|
|
||||||
finding: only Security Lists used without NSGs
|
|
||||||
recommendation: NSGs used as primary network security mechanism
|
|
||||||
- check_id: SEC-NET-004
|
|
||||||
pillar: Network Security
|
|
||||||
finding: OCI services accessed via internet gateway
|
|
||||||
recommendation: Service Gateway configured in VCN
|
|
||||||
- check_id: SEC-MON-002
|
|
||||||
pillar: Monitoring & Audit
|
|
||||||
finding: audit service not mentioned or retention not configured
|
|
||||||
recommendation: Audit service enabled (enabled by default, verify retention)
|
|
||||||
- check_id: SEC-MON-003
|
|
||||||
pillar: Monitoring & Audit
|
|
||||||
finding: no VCN Flow Logs configured
|
|
||||||
recommendation: VCN Flow Logs enabled for critical subnets
|
|
||||||
- check_id: PERF-CST-003
|
|
||||||
pillar: Cost Management
|
|
||||||
finding: no budget alerts configured
|
|
||||||
recommendation: OCI Budgets configured with alert thresholds
|
|
||||||
- check_id: PERF-CST-004
|
|
||||||
pillar: Cost Management
|
|
||||||
finding: no cost attribution tagging strategy
|
|
||||||
recommendation: cost-tracking tags defined and enforced
|
|
||||||
- check_id: OPS-MON-003
|
|
||||||
pillar: Workload Monitoring
|
|
||||||
finding: no centralized log analysis solution
|
|
||||||
recommendation: OCI Logging Analytics configured
|
|
||||||
- check_id: OPS-SUP-002
|
|
||||||
pillar: Operations Support
|
|
||||||
finding: no notification configuration
|
|
||||||
recommendation: OCI Notifications configured for operational events
|
|
||||||
low_priority:
|
|
||||||
- check_id: REL-SCL-002
|
|
||||||
pillar: Scalability
|
|
||||||
finding: no service limit review planned
|
|
||||||
recommendation: service limit review mentioned in deployment plan
|
|
||||||
- check_id: OPS-SUP-001
|
|
||||||
pillar: Operations Support
|
|
||||||
finding: no support plan consideration
|
|
||||||
recommendation: Oracle support plan level specified and matches criticality
|
|
||||||
references:
|
|
||||||
framework: https://docs.oracle.com/en/solutions/oci-best-practices/index.html
|
|
||||||
security: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-security-and-compliance1.html
|
|
||||||
reliability: https://docs.oracle.com/en/solutions/oci-best-practices/reliable-and-resilient-cloud-topology-practices1.html
|
|
||||||
operations: https://docs.oracle.com/en/solutions/oci-best-practices/best-practices-operating-cloud-deployments-efficiency.html
|
|
||||||
distributed: https://docs.oracle.com/en/solutions/oci-best-practices/effective-strategies-distributed-cloud-implementation1.html
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"feature": "Auto Indexing",
|
|
||||||
"category": "auto_management",
|
|
||||||
"compatibility": [
|
|
||||||
{
|
|
||||||
"deployment": "ADB-S Serverless",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "Creates indexes HIDDEN by default. Instantly reversible. DML costing prevents over-indexing. Deduplication with manually created indexes."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB-S Serverless",
|
|
||||||
"version": "26ai",
|
|
||||||
"status": "GA",
|
|
||||||
"notes": "Improved in 26ai \u2014 better cost model, fewer false positives."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB-S Elastic Pool",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "Same as ADB-S Serverless."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB Dedicated Elastic Pool (DEP)",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "Available but less tested in DEP."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB on Dedicated Exadata",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA",
|
|
||||||
"notes": "Fully supported."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "Exadata Cloud Service",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "NOT_AVAIL",
|
|
||||||
"notes": "Not an ADB feature. Use manual indexing."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "Base DB Service (EE)",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "NOT_AVAIL",
|
|
||||||
"notes": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"feature": "AI Vector Search (HNSW)",
|
|
||||||
"category": "developer",
|
|
||||||
"compatibility": [
|
|
||||||
{
|
|
||||||
"deployment": "ADB-S Serverless",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "HNSW indexes are node-local on multi-node RAC. Works well up to\n~50M vectors on single node (\u226424 ECPUs). At 100M+ on multi-node\n(\u226564 ECPUs), requires hash-partitioned vector table with LOCAL\nHNSW index for <50ms P95. See FF-202603-008.\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB-S Serverless",
|
|
||||||
"version": "26ai",
|
|
||||||
"status": "UNTESTED",
|
|
||||||
"notes": "Check if distributed HNSW is added in 26ai. Expected to remain node-local."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB-S Elastic Pool",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "Same node-local limitation as ADB-S Serverless."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "ADB on Dedicated Exadata",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "Same limitation. Dedicated infra doesn't change HNSW locality."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "Exadata Cloud Service",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "GA_CAVEAT",
|
|
||||||
"notes": "RAC with HNSW has same node-local constraint. Partitioning workaround applies."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deployment": "Base DB Service (EE)",
|
|
||||||
"version": "23ai",
|
|
||||||
"status": "NOT_AVAIL",
|
|
||||||
"notes": "AI Vector Search requires 23ai. Available in EE but HNSW performance limited without Exadata storage."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"query": "ADB-S vs AWS Aurora PostgreSQL",
|
|
||||||
"aws_database_mappings": [],
|
|
||||||
"relevant_objections": [
|
|
||||||
{
|
|
||||||
"id": "OBJ-001",
|
|
||||||
"category": "market",
|
|
||||||
"objection": "OCI is a niche cloud \u2014 AWS/Azure are the leaders",
|
|
||||||
"response": "OCI is the #4 cloud globally and growing fastest in database PaaS.\nFor Oracle database workloads specifically, OCI is the leader:\n- Only cloud with Exadata hardware\n- Only cloud with Autonomous Database\n- Best Oracle license portability\nThe question isn't \"best cloud overall\" but \"best cloud for this workload.\"\n",
|
|
||||||
"evidence": "Gartner, IDC reports on cloud database market"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "OBJ-004",
|
|
||||||
"category": "features",
|
|
||||||
"objection": "OCI doesn't have as many services as AWS",
|
|
||||||
"response": "True for breadth (AWS has 200+ services). For database and enterprise\nworkloads, OCI feature parity is strong. Key services are GA:\n- Compute, networking, storage, Kubernetes, serverless\n- Monitoring, logging, IAM, security\n- Queue, Streaming, API Gateway\nWhat matters is whether OCI has what *you* need, not a raw count.\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "OBJ-005",
|
|
||||||
"category": "cost",
|
|
||||||
"objection": "We can get better pricing from AWS/Azure",
|
|
||||||
"response": "OCI list prices are already 30-50% below AWS for equivalent configs.\nFor Oracle specifically:\n- No Oracle license surcharge on OCI\n- BYOL discount is deeper on OCI\n- Data egress is 10x cheaper\nRequest a detailed cost comparison with matched configurations.\n"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "FF-202603-002",
|
|
||||||
"date": "2026-03-08",
|
|
||||||
"contributor": {
|
|
||||||
"name": "Diego Cabrera",
|
|
||||||
"team": "Field Architecture",
|
|
||||||
"client": "Strategic Migration Customer",
|
|
||||||
"context": "Production migration",
|
|
||||||
"confidence": "validated"
|
|
||||||
},
|
|
||||||
"product": "ADB-S Dedicated Elastic Pool (DEP)",
|
|
||||||
"version": "23ai",
|
|
||||||
"severity": "HIGH",
|
|
||||||
"category": "limitation",
|
|
||||||
"summary": "ADG must be disabled before joining Dedicated Elastic Pool",
|
|
||||||
"detail": "When attempting to add an ADB-S instance with active Autonomous Data Guard\nto a Dedicated Elastic Pool, the operation fails. ADG must be explicitly\ndisabled before the instance can join the DEP. This is a known issue\nacknowledged by Oracle but not prominently documented.\n\nImpact: requires a brief HA gap during DEP onboarding. Must coordinate\nwith the customer's change window.\n",
|
|
||||||
"workaround": "Disable ADG \u2192 join DEP \u2192 re-enable ADG. Plan for ~15 min HA gap.",
|
|
||||||
"oracle_sr": "",
|
|
||||||
"status": "acknowledged",
|
|
||||||
"resolved_date": null,
|
|
||||||
"resolution": null,
|
|
||||||
"affects_matrix": "Elastic Pool Membership",
|
|
||||||
"tags": [
|
|
||||||
"dep",
|
|
||||||
"adg",
|
|
||||||
"elastic-pool",
|
|
||||||
"ha",
|
|
||||||
"limitation"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FF-202603-003",
|
|
||||||
"date": "2026-03-05",
|
|
||||||
"contributor": {
|
|
||||||
"name": "Diego Cabrera",
|
|
||||||
"team": "Field Architecture",
|
|
||||||
"client": "Strategic Migration Customer",
|
|
||||||
"context": "Production migration",
|
|
||||||
"confidence": "validated"
|
|
||||||
},
|
|
||||||
"product": "ADB-S Dedicated Elastic Pool (DEP)",
|
|
||||||
"version": "23ai",
|
|
||||||
"severity": "MEDIUM",
|
|
||||||
"category": "undocumented",
|
|
||||||
"summary": "DEP maintenance window immutable post-creation \u2014 change only via SR",
|
|
||||||
"detail": "The maintenance window for a Dedicated Elastic Pool cannot be changed\nafter creation via Console, CLI, or API. The only way to change it is\nby filing a Service Request with Oracle Support.\n\nDefault window is Saturday/Sunday. Other days require specifying at\ncreation time (which itself has the CLI bug \u2014 see FF-202603-001) or\nrequesting via SR.\n",
|
|
||||||
"workaround": "Set desired window at DEP creation time (via Console, not CLI). If missed, file SR.",
|
|
||||||
"oracle_sr": "",
|
|
||||||
"status": "acknowledged",
|
|
||||||
"resolved_date": null,
|
|
||||||
"resolution": null,
|
|
||||||
"affects_matrix": "Auto Patching",
|
|
||||||
"tags": [
|
|
||||||
"dep",
|
|
||||||
"maintenance-window",
|
|
||||||
"immutable",
|
|
||||||
"sr-required"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FF-202603-004",
|
|
||||||
"date": "2026-03-01",
|
|
||||||
"contributor": {
|
|
||||||
"name": "Diego Cabrera",
|
|
||||||
"team": "Field Architecture",
|
|
||||||
"client": "Strategic Migration Customer",
|
|
||||||
"context": "Production migration",
|
|
||||||
"confidence": "validated"
|
|
||||||
},
|
|
||||||
"product": "ADB-S",
|
|
||||||
"version": "23ai",
|
|
||||||
"severity": "MEDIUM",
|
|
||||||
"category": "gotcha",
|
|
||||||
"summary": "DEP provisioning takes days to weeks depending on capacity",
|
|
||||||
"detail": "Dedicated Elastic Pool provisioning is NOT instant like ADB-S Serverless.\nIt requires physical Exadata infrastructure allocation. Lead time varies\nfrom 3 days (if capacity available in region) to 2-3 weeks (if capacity\nneeds to be provisioned).\n\nBilling starts only at AVAILABLE state, not at request time.\nCo-location of existing ADB-S instances happens at the next maintenance\nwindow after joining, not immediately.\n",
|
|
||||||
"workaround": "Request DEP early in the project timeline. Don't make it a critical-path dependency in week 1.",
|
|
||||||
"oracle_sr": "",
|
|
||||||
"status": "acknowledged",
|
|
||||||
"resolved_date": null,
|
|
||||||
"resolution": null,
|
|
||||||
"affects_matrix": "Elastic Pool Membership",
|
|
||||||
"tags": [
|
|
||||||
"dep",
|
|
||||||
"provisioning",
|
|
||||||
"lead-time",
|
|
||||||
"capacity",
|
|
||||||
"planning"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "FF-202603-005",
|
|
||||||
"date": "2026-02-20",
|
|
||||||
"contributor": {
|
|
||||||
"name": "Diego Cabrera",
|
|
||||||
"team": "Field Architecture",
|
|
||||||
"client": "Strategic Migration Customer",
|
|
||||||
"context": "Production migration",
|
|
||||||
"confidence": "observed"
|
|
||||||
},
|
|
||||||
"product": "ADB-S",
|
|
||||||
"version": "23ai",
|
|
||||||
"severity": "LOW",
|
|
||||||
"category": "gotcha",
|
|
||||||
"summary": "Refreshable Clone failed refresh leaves clone in AVAILABLE state \u2014 detection delay",
|
|
||||||
"detail": "When a Refreshable Clone's auto-refresh fails, the clone remains in\nAVAILABLE state (not DISCONNECTED). The Elastic Pool leader monitors\nlifecycle state changes, not refresh success/failure.\n\nThis means a failed refresh is NOT immediately detected by the\ninfrastructure. The stale data window equals the ADG RTO for\nunplanned scenarios.\n",
|
|
||||||
"workaround": "Planned switchovers: DBMS_PROXY_SQL.DISABLE_READ_ONLY_OFFLOAD proactively.\nUnplanned: OCI Events + OCI Function that monitors refresh state and\ndisables read offload automatically. Reduces stale window to ~ADG RTO.\n",
|
|
||||||
"oracle_sr": "",
|
|
||||||
"status": "monitoring",
|
|
||||||
"resolved_date": null,
|
|
||||||
"resolution": null,
|
|
||||||
"affects_matrix": "Refreshable Clone",
|
|
||||||
"tags": [
|
|
||||||
"refreshable-clone",
|
|
||||||
"stale-reads",
|
|
||||||
"elastic-pool",
|
|
||||||
"detection-delay",
|
|
||||||
"proxysql"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"title": "Oracle MAA for Oracle Database@Azure",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/oracle-maa-db-at-azure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-05",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"vault",
|
|
||||||
"azure"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"azure",
|
|
||||||
"ha-dr",
|
|
||||||
"autonomous"
|
|
||||||
],
|
|
||||||
"summary": "Cross-AZ Data Guard on ExaCS in Database@Azure. Active Data Guard recommended for cross-AZ replication (block repair, app continuity, read offload). Backups to Autonomous Recovery Service.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement disaster recovery with local and regional standbys on Oracle Database@Azure",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/local-regional-standby-dr-db-at-azure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-05",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"azure",
|
|
||||||
"fsdr"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"azure",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Local standby for HA within same region, regional standby for DR across Azure regions. Uses Active Data Guard with Fast-Start Failover. Recommends Full Stack DR service for automated failover orchestration.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement disaster recovery with multi-region standby on Oracle Database@Azure",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/multi-region-standby-dr-db-at-azure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-04",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"azure"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"azure",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Multi-region DR for ExaCS on Database@Azure using Data Guard. Cross-region standby with switchover/failover capabilities. Includes network topology for Azure-to-Azure cross-region connectivity.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement cross-region disaster recovery for Exadata Database on Oracle Database@Azure",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/exadb-dr-on-db-azure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-01",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"azure",
|
|
||||||
"fsdr"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"azure",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Cross-region DR for ExaCS on Database@Azure. OCI-managed networks for peering (better performance, first 10 TB/month free). Data Guard for cross-region replication, FSDR for automated failover orchestration.\n",
|
|
||||||
"terraform": "https://github.com/oracle-quickstart/oci-multicloud-azure"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Deploy Active Data Guard Far Sync to protect data across Oracle Database@Azure regions",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/active-data-guard-far-sync-oracle-db-at-azure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-02",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"azure"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"azure",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Far Sync instance for zero data loss protection across Azure regions. Synchronous redo transport to Far Sync, async to remote standby. Eliminates performance impact of synchronous shipping over long distances.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement Oracle Database Autonomous Recovery Service with Oracle Database@Azure",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/implement-recovery-service-db-at-azure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-01",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"azure"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"azure",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Autonomous Recovery Service for ExaCS backups on Database@Azure. Automated backup management with real-time protection and point-in-time recovery capabilities.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Deploy Active Data Guard Far Sync to protect data across Oracle Database@AWS regions",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/dr-active-dg-farsync-db-at-aws/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-09",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"aws"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"aws",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Far Sync instance for zero data loss protection across AWS regions. Synchronous redo to local Far Sync, asynchronous to remote standby. Requires cross-region AWS networking for redo transport.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement disaster recovery with local and regional standbys on Oracle Database@AWS",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/dr-local-regional-standby-db-at-aws/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-09",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"aws",
|
|
||||||
"fsdr"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"aws",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Local standby for HA within same AWS region, regional standby for DR. Active Data Guard with Fast-Start Failover for automated failover. Full Stack DR service for cross-service failover orchestration.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement disaster recovery with cross-zonal Data Guard on Oracle Database@AWS",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/cross-zone-dr-db-at-aws/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-08",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"aws"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"aws",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Cross-AZ Data Guard on Database@AWS for HA. Synchronous redo transport between AZs for zero data loss. Fast-Start Failover for automatic database switchover on failure.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Implement disaster recovery with cross-regional Data Guard on Oracle Database@AWS",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/implement-dr-cross-region-dg-odb-aws/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-08",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"aws"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"multicloud",
|
|
||||||
"aws",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Cross-region Data Guard on Database@AWS for DR. Asynchronous redo transport for cross-region replication. Planned switchover for maintenance, forced failover for disaster scenarios.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Migrate Oracle RAC Databases to OCI",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/ensure-ha-migrate-vmware-workloads-to-oci/migrate-oracle-rac-databases1.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-01",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"exascale",
|
|
||||||
"adb-d",
|
|
||||||
"base-db"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"migration",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Compares 4 RAC migration targets: ExaCS Dedicated, Exascale, ADB-D, and 2-node RAC on Base DB. ExaCS recommended for full RAC feature parity. Exascale for 23ai-only without dedicated infra commitment.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Configure Data Guard for Oracle Exadata Database Service on Dedicated Infrastructure",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/dataguard-exadata-dedicated-infrastructure/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-07",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Data Guard configuration for ExaCS Dedicated. Active Data Guard for read offload and automatic failover. Cross-region standby for DR with Fast-Start Failover for automated switchover.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Back up from Exadata on Dedicated Infrastructure to Zero Data Loss Autonomous Recovery",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/back-up-exadata-dedicated-zero-data-loss-recovery/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-07",
|
|
||||||
"services": [
|
|
||||||
"exacs"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Autonomous Recovery Service for ExaCS backups. Automated backup management with real-time redo protection. Validates backup recoverability automatically.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Configure a standby database for disaster recovery using Oracle Exadata Database Service on Cloud@Customer",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/exadata-cloud-at-customer-standby/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2025-07",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"database",
|
|
||||||
"ha-dr"
|
|
||||||
],
|
|
||||||
"summary": "Data Guard standby on ExaCS Cloud@Customer for hybrid DR. On-premises primary with cloud standby, or cloud primary with on-premises standby for data sovereignty requirements.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Learn about maximum availability architecture for PeopleSoft",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/learn-about-maa-for-peoplesoft/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2024-08",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"load-balancer",
|
|
||||||
"compute"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"application",
|
|
||||||
"ha-dr",
|
|
||||||
"peoplesoft"
|
|
||||||
],
|
|
||||||
"summary": "MAA for PeopleSoft on OCI. Active Data Guard for database HA/DR, multi-instance PIA behind Load Balancer, Process Scheduler on multiple nodes for batch processing resilience.\n",
|
|
||||||
"terraform": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Provision and deploy a maximum availability solution for PeopleSoft on Oracle Cloud",
|
|
||||||
"url": "https://docs.oracle.com/en/solutions/deploy-maa-for-peoplesoft-on-oci/index.html",
|
|
||||||
"type": "reference-architecture",
|
|
||||||
"date": "2024-08",
|
|
||||||
"services": [
|
|
||||||
"exacs",
|
|
||||||
"adg",
|
|
||||||
"load-balancer",
|
|
||||||
"compute"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"application",
|
|
||||||
"ha-dr",
|
|
||||||
"peoplesoft"
|
|
||||||
],
|
|
||||||
"summary": "Step-by-step PeopleSoft MAA deployment on OCI. Terraform stacks for infrastructure provisioning, Data Guard configuration, and PeopleSoft application tier setup.\n",
|
|
||||||
"terraform": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "FF-202604-001",
|
|
||||||
"date": "2026-04-12",
|
|
||||||
"contributor": {
|
|
||||||
"name": "Carlos Mendoza",
|
|
||||||
"team": "Field Architecture",
|
|
||||||
"date": "2026-04-12",
|
|
||||||
"confidence": "validated",
|
|
||||||
"client": "PharmaCorp Mexico",
|
|
||||||
"context": "ExaCS migration"
|
|
||||||
},
|
|
||||||
"reported_by": "Carlos Mendoza",
|
|
||||||
"product": "ExaCS",
|
|
||||||
"version": "23ai",
|
|
||||||
"severity": "MEDIUM",
|
|
||||||
"category": "gotcha",
|
|
||||||
"summary": "ExaCS iormcal fails silently when storage server count < 3 \u2014 validate IORM plan after apply",
|
|
||||||
"detail": "IORM resource plan apply succeeds but actual I/O shares are not enforced when storage count < 3. Check v$iorm_plans for actual vs requested allocation.",
|
|
||||||
"workaround": "Always validate IORM plan output after apply. Check v$iorm_plans for actual vs requested shares. Minimum 3 storage servers for reliable IORM.",
|
|
||||||
"oracle_sr": "",
|
|
||||||
"status": "open",
|
|
||||||
"resolved_date": null,
|
|
||||||
"resolution": null,
|
|
||||||
"affects_matrix": null,
|
|
||||||
"tags": [
|
|
||||||
"exacs",
|
|
||||||
"iorm",
|
|
||||||
"storage"
|
|
||||||
],
|
|
||||||
"confirmations": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": {
|
|
||||||
"total_artefacts": 18,
|
|
||||||
"mentioned": 2,
|
|
||||||
"likely_relevant": 0,
|
|
||||||
"possibly_relevant": 9,
|
|
||||||
"not_covered": 7,
|
|
||||||
"skill_auto_generate": 5,
|
|
||||||
"skill_assist": 2
|
|
||||||
},
|
|
||||||
"artefacts": [
|
|
||||||
{
|
|
||||||
"id": "DEF-01",
|
|
||||||
"name": "Customer Profile",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "ideate",
|
|
||||||
"skill_support": "assist",
|
|
||||||
"customer_facing": false,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-02",
|
|
||||||
"name": "Strategy Map",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "ideate",
|
|
||||||
"skill_support": "assist",
|
|
||||||
"customer_facing": false,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-03",
|
|
||||||
"name": "Point of View (PoV)",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "ideate",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": false,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-04",
|
|
||||||
"name": "Ideation Workshop",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "ideate",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-05",
|
|
||||||
"name": "Business Case",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "ideate",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "mentioned"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-06",
|
|
||||||
"name": "Value Story",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "ideate",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "mentioned"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-07",
|
|
||||||
"name": "Value Story Delivery",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "validate",
|
|
||||||
"skill_support": "assist",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEF-08",
|
|
||||||
"name": "Joint Engagement Plan (JEP)",
|
|
||||||
"phase": "define",
|
|
||||||
"step": "plan",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DES-35",
|
|
||||||
"name": "Solution Proposal",
|
|
||||||
"phase": "design",
|
|
||||||
"step": "confirm",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DES-36",
|
|
||||||
"name": "Partner / Services Proposal",
|
|
||||||
"phase": "design",
|
|
||||||
"step": "confirm",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DES-37",
|
|
||||||
"name": "RFx Response",
|
|
||||||
"phase": "design",
|
|
||||||
"step": "confirm",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DES-38",
|
|
||||||
"name": "Commercial Agreement",
|
|
||||||
"phase": "design",
|
|
||||||
"step": "confirm",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEL-17",
|
|
||||||
"name": "Cloud Operating Framework",
|
|
||||||
"phase": "deliver",
|
|
||||||
"step": "operate",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEL-18",
|
|
||||||
"name": "OCI Operationalization Framework",
|
|
||||||
"phase": "deliver",
|
|
||||||
"step": "operate",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEL-19",
|
|
||||||
"name": "Retrospective",
|
|
||||||
"phase": "deliver",
|
|
||||||
"step": "improve",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": false,
|
|
||||||
"relevance": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEL-20",
|
|
||||||
"name": "Value Realization",
|
|
||||||
"phase": "deliver",
|
|
||||||
"step": "improve",
|
|
||||||
"skill_support": "auto_generate",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEL-21",
|
|
||||||
"name": "Customer Success Story",
|
|
||||||
"phase": "deliver",
|
|
||||||
"step": "improve",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "DEL-22",
|
|
||||||
"name": "Improvement Actions",
|
|
||||||
"phase": "deliver",
|
|
||||||
"step": "improve",
|
|
||||||
"skill_support": "reference",
|
|
||||||
"customer_facing": true,
|
|
||||||
"relevance": "possibly_relevant"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
# OCI Deal Accelerator — Proposal Spec
|
|
||||||
# Demo: Pharma Mexico — ExaCS migration from on-prem Exadata X6
|
|
||||||
# Prompt 1: Full proposal
|
|
||||||
|
|
||||||
metadata:
|
|
||||||
customer: "PharmaCorp Mexico"
|
|
||||||
project: "Oracle Database Migration to OCI"
|
|
||||||
subtitle: "ExaCS Migration — Exadata X6 to OCI"
|
|
||||||
architect: "Carlos Mendoza"
|
|
||||||
firm: "Oracle Cloud Infrastructure"
|
|
||||||
date: "2026-04-12"
|
|
||||||
|
|
||||||
summary:
|
|
||||||
why: >
|
|
||||||
PharmaCorp Mexico operates 3 Oracle databases (EBS R12, SAP, DW) on on-premises
|
|
||||||
Exadata X6 hardware approaching end-of-life. Hardware refresh costs $1.8M and
|
|
||||||
locks into 5 more years of on-prem operations. Migration to OCI ExaCS provides
|
|
||||||
30%+ cost reduction, eliminates hardware lifecycle management, improves DR posture
|
|
||||||
(RPO<15min, RTO<1h), and enables SOX compliance alignment with Oracle-managed
|
|
||||||
infrastructure patching.
|
|
||||||
current_state:
|
|
||||||
- "3 Oracle DBs on Exadata X6 (EBS R12 prod, SAP, Data Warehouse)"
|
|
||||||
- "200 concurrent users, peak during fiscal close"
|
|
||||||
- "2TB total data across all databases"
|
|
||||||
- "No DR — single site in Mexico City data center"
|
|
||||||
- "Manual patching quarterly — SOX audit finding pending"
|
|
||||||
- "HW refresh due in 6 months ($1.8M quote)"
|
|
||||||
target_state: >
|
|
||||||
OCI ExaCS X11M in Queretaro (prod) with Data Guard standby in Sao Paulo for
|
|
||||||
cross-region DR. Hub-spoke networking via DRG, dual FastConnect 10Gbps for
|
|
||||||
redundancy, WAF for web applications, OCI Vault for TDE key management.
|
|
||||||
SOX compliance via automated patching and audit logging.
|
|
||||||
timeline: "Migration Q3-Q4 2026 (16 weeks) — conditional on board approval Q2"
|
|
||||||
|
|
||||||
service_tiering:
|
|
||||||
- name: "EBS R12 Production"
|
|
||||||
tier: "Platinum"
|
|
||||||
uptime: "99.99%"
|
|
||||||
rto: "1 hour"
|
|
||||||
rpo: "15 minutes"
|
|
||||||
justification: "Business-critical ERP — fiscal close depends on this"
|
|
||||||
- name: "SAP Database"
|
|
||||||
tier: "Gold"
|
|
||||||
uptime: "99.95%"
|
|
||||||
rto: "4 hours"
|
|
||||||
rpo: "1 hour"
|
|
||||||
justification: "Critical but lower user count"
|
|
||||||
- name: "Data Warehouse"
|
|
||||||
tier: "Silver"
|
|
||||||
uptime: "99.9%"
|
|
||||||
rto: "8 hours"
|
|
||||||
rpo: "4 hours"
|
|
||||||
justification: "Reporting — can tolerate longer recovery"
|
|
||||||
|
|
||||||
architecture_principles:
|
|
||||||
design:
|
|
||||||
- id: "D-01"
|
|
||||||
name: "Operational Simplicity"
|
|
||||||
summary: "Prefer managed services with automated patching"
|
|
||||||
rationale: "SOX requires documented patching — ExaCS automates this"
|
|
||||||
- id: "D-05"
|
|
||||||
name: "HA for Business Continuity"
|
|
||||||
summary: "Design for HA proportional to business impact"
|
|
||||||
rationale: "EBS is Platinum tier — needs cross-region DR"
|
|
||||||
- id: "D-06"
|
|
||||||
name: "Defense in Depth"
|
|
||||||
summary: "Multiple security layers, not single perimeter"
|
|
||||||
rationale: "SOX compliance requires network segmentation + encryption + audit"
|
|
||||||
deployment:
|
|
||||||
- id: "P-01"
|
|
||||||
name: "Infrastructure as Code"
|
|
||||||
summary: "All infrastructure defined in Terraform"
|
|
||||||
rationale: "Reproducible environments, audit trail for SOX"
|
|
||||||
|
|
||||||
architecture:
|
|
||||||
description: >
|
|
||||||
Dual-region ExaCS deployment: Queretaro (primary) + Sao Paulo (DR standby).
|
|
||||||
Hub-spoke networking with DRG provides segmented VCNs for each workload tier.
|
|
||||||
Dual FastConnect 10Gbps from Mexico City provides redundant connectivity.
|
|
||||||
WAF protects EBS web tier. OCI Vault manages TDE encryption keys externally.
|
|
||||||
|
|
||||||
Key components:
|
|
||||||
- ExaCS X11M Quarter Rack in Queretaro (3 databases)
|
|
||||||
- Data Guard to ExaCS standby in Sao Paulo (EBS + SAP)
|
|
||||||
- Hub VCN with DRG for inter-spoke routing
|
|
||||||
- Dual FastConnect 10Gbps from Mexico City DC
|
|
||||||
- WAF for EBS R12 web access
|
|
||||||
- OCI Vault for TDE key management (SOX)
|
|
||||||
- OCI Logging + Audit for compliance
|
|
||||||
visual:
|
|
||||||
regions:
|
|
||||||
- name: "Queretaro (Primary)"
|
|
||||||
services:
|
|
||||||
- name: "ExaCS X11M (3 DBs)"
|
|
||||||
type: "database"
|
|
||||||
- name: "Hub VCN + DRG"
|
|
||||||
type: "networking"
|
|
||||||
- name: "WAF"
|
|
||||||
type: "security"
|
|
||||||
- name: "OCI Vault"
|
|
||||||
type: "security"
|
|
||||||
- name: "Sao Paulo (DR)"
|
|
||||||
services:
|
|
||||||
- name: "ExaCS Standby (Data Guard)"
|
|
||||||
type: "database"
|
|
||||||
- name: "Spoke VCN"
|
|
||||||
type: "networking"
|
|
||||||
|
|
||||||
decisions:
|
|
||||||
- title: "ExaCS over ADB-S"
|
|
||||||
decision: "Use ExaCS instead of ADB-S for all 3 databases"
|
|
||||||
rationale: >
|
|
||||||
EBS R12 requires OS-level access for patches and customizations.
|
|
||||||
SAP on Oracle requires specific Oracle features not available on ADB-S.
|
|
||||||
DW benefits from ExaCS storage performance. All 3 share the same
|
|
||||||
Exadata infrastructure for cost efficiency.
|
|
||||||
alternatives: "ADB-S (rejected — EBS needs OS access), DBCS (rejected — no Exadata performance)"
|
|
||||||
|
|
||||||
- title: "Cross-Region DR with Data Guard"
|
|
||||||
decision: "Active Data Guard from Queretaro to Sao Paulo"
|
|
||||||
rationale: >
|
|
||||||
RPO<15min requires real-time redo shipping. Sao Paulo provides geographic
|
|
||||||
separation for disaster scenarios. Data Guard supports automatic failover
|
|
||||||
with FSDR for RTO<1h.
|
|
||||||
alternatives: "Backup/restore (rejected — RPO 24h), Same-region standby (no geo separation)"
|
|
||||||
|
|
||||||
- title: "Hub-Spoke Networking"
|
|
||||||
decision: "Hub VCN with DRG for inter-spoke routing and segmentation"
|
|
||||||
rationale: >
|
|
||||||
SOX requires network segmentation between environments. Hub-spoke with DRG
|
|
||||||
provides centralized security controls (WAF, NSG) while isolating workloads.
|
|
||||||
alternatives: "Flat VCN (rejected — no segmentation for SOX)"
|
|
||||||
|
|
||||||
- title: "Dual FastConnect"
|
|
||||||
decision: "Two 10Gbps FastConnect circuits from Mexico City"
|
|
||||||
rationale: >
|
|
||||||
Single FastConnect is a documented single point of failure. Dual circuits
|
|
||||||
provide redundancy for the EBS production workload.
|
|
||||||
alternatives: "Single FastConnect + IPSec VPN backup (acceptable for lower tiers)"
|
|
||||||
|
|
||||||
- title: "OCI Vault for TDE Keys"
|
|
||||||
decision: "External key management via OCI Vault instead of Oracle Wallet"
|
|
||||||
rationale: >
|
|
||||||
SOX requires separation of duties for encryption key management.
|
|
||||||
OCI Vault provides FIPS 140-2 Level 3 HSM-backed key storage.
|
|
||||||
alternatives: "Oracle Wallet on ExaCS (simpler but no separation of duties)"
|
|
||||||
|
|
||||||
ha_dr:
|
|
||||||
description: >
|
|
||||||
Primary in Queretaro with Active Data Guard to Sao Paulo. FSDR orchestrates
|
|
||||||
failover for RTO<1h. EBS and SAP databases replicated; DW rebuilt from backup
|
|
||||||
in DR scenario (Silver tier — 8h RTO acceptable).
|
|
||||||
tiers:
|
|
||||||
- tier: "Platinum — EBS R12"
|
|
||||||
technology: "ExaCS + Active Data Guard + FSDR automatic failover"
|
|
||||||
rto: "1 hour"
|
|
||||||
rpo: "15 minutes"
|
|
||||||
- tier: "Gold — SAP"
|
|
||||||
technology: "ExaCS + Active Data Guard"
|
|
||||||
rto: "4 hours"
|
|
||||||
rpo: "1 hour"
|
|
||||||
- tier: "Silver — DW"
|
|
||||||
technology: "ExaCS + RMAN backup to Object Storage"
|
|
||||||
rto: "8 hours"
|
|
||||||
rpo: "4 hours"
|
|
||||||
|
|
||||||
security:
|
|
||||||
controls:
|
|
||||||
identity:
|
|
||||||
- "OCI IAM with federated identity (SAML 2.0 to corporate AD)"
|
|
||||||
- "Separate admin compartments per environment"
|
|
||||||
- "MFA required for all admin operations"
|
|
||||||
- "Break-glass emergency access procedures"
|
|
||||||
network:
|
|
||||||
- "Hub-spoke VCN with DRG — workload isolation"
|
|
||||||
- "WAF for EBS R12 web tier"
|
|
||||||
- "NSG rules per spoke — deny-all default"
|
|
||||||
- "Dual FastConnect — no public internet exposure"
|
|
||||||
data_protection:
|
|
||||||
- "TDE encryption at rest with OCI Vault (FIPS 140-2 L3)"
|
|
||||||
- "Encryption in transit (TLS 1.2+ / native Oracle Net encryption)"
|
|
||||||
- "Data Guard redo encryption"
|
|
||||||
monitoring:
|
|
||||||
- "OCI Audit for all API calls (SOX)"
|
|
||||||
- "OCI Logging for network flows"
|
|
||||||
- "Database Audit with Unified Auditing"
|
|
||||||
- "OCI Events + Notifications for security alerts"
|
|
||||||
compliance:
|
|
||||||
- "SOX — automated patching, audit logging, separation of duties"
|
|
||||||
|
|
||||||
environment_catalogue:
|
|
||||||
environments:
|
|
||||||
- name: "Production (Queretaro)"
|
|
||||||
sizing: "ExaCS X11M Quarter Rack — 3 databases (EBS, SAP, DW)"
|
|
||||||
isolation: "Dedicated Exadata infrastructure"
|
|
||||||
cost_pct: 100
|
|
||||||
- name: "DR Standby (Sao Paulo)"
|
|
||||||
sizing: "ExaCS X11M Quarter Rack — 2 databases (EBS, SAP standby)"
|
|
||||||
isolation: "Dedicated Exadata infrastructure"
|
|
||||||
cost_pct: 70
|
|
||||||
- name: "Pre-Production"
|
|
||||||
sizing: "DBCS VM — reduced sizing for testing"
|
|
||||||
isolation: "Shared infrastructure, separate compartment"
|
|
||||||
cost_pct: 15
|
|
||||||
cost_notes:
|
|
||||||
- "DR standby runs at 70% — Active Data Guard requires matching shape"
|
|
||||||
- "Pre-prod uses DBCS VMs, not ExaCS — sufficient for functional testing"
|
|
||||||
- "Dev/Test environment excluded — customer uses on-prem for dev"
|
|
||||||
|
|
||||||
cost:
|
|
||||||
line_items:
|
|
||||||
- component: "ExaCS X11M Quarter Rack — Queretaro (Prod)"
|
|
||||||
monthly_payg: "$18,500"
|
|
||||||
monthly_byol: "$8,200"
|
|
||||||
notes: "2 DB servers, 3 storage servers, BYOL"
|
|
||||||
- component: "ExaCS X11M Quarter Rack — Sao Paulo (DR)"
|
|
||||||
monthly_payg: "$18,500"
|
|
||||||
monthly_byol: "$8,200"
|
|
||||||
notes: "ADG standby, BYOL"
|
|
||||||
- component: "ADB-S 16 ECPU (DW alternative — future)"
|
|
||||||
monthly_payg: "$2,400"
|
|
||||||
monthly_byol: "$1,200"
|
|
||||||
notes: "Optional — migrate DW to ADB-S in Phase 2"
|
|
||||||
- component: "FastConnect 10Gbps Dual"
|
|
||||||
monthly_payg: "$3,060"
|
|
||||||
notes: "2 x $1,530/month"
|
|
||||||
- component: "OCI Vault"
|
|
||||||
monthly_payg: "$500"
|
|
||||||
notes: "HSM-backed key management"
|
|
||||||
- component: "Object Storage (50TB backup)"
|
|
||||||
monthly_payg: "$1,200"
|
|
||||||
notes: "RMAN backups + DW backup"
|
|
||||||
- component: "WAF + Logging + Monitoring"
|
|
||||||
monthly_payg: "$800"
|
|
||||||
notes: "WAF for EBS web tier + audit logging"
|
|
||||||
- component: "TOTAL"
|
|
||||||
monthly_payg: "$45,000"
|
|
||||||
monthly_byol: "$23,200"
|
|
||||||
notes: "BYOL saves 48%"
|
|
||||||
assumptions:
|
|
||||||
- "BYOL assumes existing Oracle Database EE licenses with active support"
|
|
||||||
- "ExaCS pricing based on current OCI list prices"
|
|
||||||
- "DR standby at full rate (ADG requires active compute)"
|
|
||||||
- "No middleware licensing included (EBS app tier on compute VMs)"
|
|
||||||
- "FastConnect partner charges not included"
|
|
||||||
show_byol: true
|
|
||||||
|
|
||||||
cost_comparison:
|
|
||||||
title: "Cost Comparison: On-Prem HW Refresh vs OCI Migration"
|
|
||||||
col_headers: ["Component", "On-Prem Refresh (5yr)", "OCI ExaCS BYOL", "Delta"]
|
|
||||||
rows:
|
|
||||||
- item: "Infrastructure (hardware/cloud)"
|
|
||||||
current: "$1,800,000 upfront"
|
|
||||||
oci: "$278,400/yr"
|
|
||||||
savings: "Break-even at 3.2 years"
|
|
||||||
- item: "Annual Support & Maintenance"
|
|
||||||
current: "$220,000/yr"
|
|
||||||
oci: "Included in OCI"
|
|
||||||
savings: "-100%"
|
|
||||||
- item: "DR Infrastructure"
|
|
||||||
current: "$0 (no DR today)"
|
|
||||||
oci: "$98,400/yr"
|
|
||||||
savings: "New capability — no DR today"
|
|
||||||
- item: "Data Center (power/cooling/space)"
|
|
||||||
current: "$80,000/yr"
|
|
||||||
oci: "$0"
|
|
||||||
savings: "-100%"
|
|
||||||
- item: "DBA Operations (patching)"
|
|
||||||
current: "$60,000/yr (part-time DBA)"
|
|
||||||
oci: "$0 (automated)"
|
|
||||||
savings: "-100%"
|
|
||||||
- item: "TOTAL ANNUAL (year 1)"
|
|
||||||
current: "$2,160,000"
|
|
||||||
oci: "$376,800"
|
|
||||||
savings: "-83%"
|
|
||||||
- item: "TOTAL 3-YEAR TCO"
|
|
||||||
current: "$2,880,000"
|
|
||||||
oci: "$1,130,400"
|
|
||||||
savings: "-$1.75M (61%)"
|
|
||||||
|
|
||||||
migration:
|
|
||||||
phases:
|
|
||||||
- name: "Phase 1: Foundation"
|
|
||||||
duration: "4 weeks"
|
|
||||||
milestones:
|
|
||||||
- "OCI tenancy + networking provisioned"
|
|
||||||
- "ExaCS Quarter Rack deployed in Queretaro"
|
|
||||||
- "FastConnect dual circuits active"
|
|
||||||
- "OCI Vault + IAM configured"
|
|
||||||
- name: "Phase 2: EBS Migration"
|
|
||||||
duration: "6 weeks"
|
|
||||||
milestones:
|
|
||||||
- "EBS R12 database migrated via ZDM"
|
|
||||||
- "Data Guard to Sao Paulo configured"
|
|
||||||
- "WAF + network security validated"
|
|
||||||
- "SOX audit controls verified"
|
|
||||||
- name: "Phase 3: SAP + DW Migration"
|
|
||||||
duration: "4 weeks"
|
|
||||||
milestones:
|
|
||||||
- "SAP database migrated"
|
|
||||||
- "DW migrated + Data Guard for SAP"
|
|
||||||
- "RMAN backup for DW to Object Storage"
|
|
||||||
- name: "Phase 4: Cutover + Decommission"
|
|
||||||
duration: "2 weeks"
|
|
||||||
milestones:
|
|
||||||
- "Production cutover (planned downtime window)"
|
|
||||||
- "On-prem Exadata X6 decommissioned"
|
|
||||||
- "Go-live monitoring active"
|
|
||||||
tools:
|
|
||||||
- "Oracle Zero Downtime Migration (ZDM)"
|
|
||||||
- "Data Guard Broker for DR setup"
|
|
||||||
- "FSDR for failover orchestration"
|
|
||||||
- "Terraform OCI provider for IaC"
|
|
||||||
downtime: "Planned 4-hour maintenance window for final cutover per database"
|
|
||||||
|
|
||||||
operational_raci:
|
|
||||||
model: "co_managed"
|
|
||||||
raci_items:
|
|
||||||
- activity: "ExaCS Infrastructure Patching"
|
|
||||||
oracle: "R"
|
|
||||||
customer: "I"
|
|
||||||
- activity: "Database Patching (quarterly)"
|
|
||||||
oracle: "C"
|
|
||||||
customer: "R"
|
|
||||||
- activity: "Data Guard Monitoring"
|
|
||||||
oracle: "C"
|
|
||||||
customer: "R"
|
|
||||||
- activity: "Backup & Recovery"
|
|
||||||
oracle: "R"
|
|
||||||
customer: "C"
|
|
||||||
- activity: "Security (IAM, NSG, Vault)"
|
|
||||||
oracle: "C"
|
|
||||||
customer: "R"
|
|
||||||
- activity: "SOX Audit Evidence"
|
|
||||||
oracle: "C"
|
|
||||||
customer: "R"
|
|
||||||
- activity: "Capacity Planning"
|
|
||||||
oracle: "C"
|
|
||||||
customer: "R"
|
|
||||||
- activity: "Incident Response (Sev1)"
|
|
||||||
oracle: "C"
|
|
||||||
customer: "R"
|
|
||||||
|
|
||||||
risks:
|
|
||||||
- risk: "EBS R12 customizations may require OS-level changes post-migration"
|
|
||||||
severity: "HIGH"
|
|
||||||
probability: "Medium"
|
|
||||||
mitigation: "Inventory all customizations pre-migration; test in Pre-Prod"
|
|
||||||
- risk: "FastConnect provisioning delay (partner-dependent)"
|
|
||||||
severity: "MEDIUM"
|
|
||||||
probability: "Medium"
|
|
||||||
mitigation: "Start FastConnect provisioning in Phase 1 week 1; IPSec VPN as interim"
|
|
||||||
- risk: "SAP certification on ExaCS version"
|
|
||||||
severity: "MEDIUM"
|
|
||||||
probability: "Low"
|
|
||||||
mitigation: "Verify SAP Note for ExaCS X11M support before migration"
|
|
||||||
- risk: "Data Guard network latency Queretaro-Sao Paulo"
|
|
||||||
severity: "LOW"
|
|
||||||
probability: "Low"
|
|
||||||
mitigation: "Test redo apply lag; async mode acceptable for SAP (Gold tier)"
|
|
||||||
- risk: "SOX audit during migration window"
|
|
||||||
severity: "MEDIUM"
|
|
||||||
probability: "Medium"
|
|
||||||
mitigation: "Maintain dual-write during transition; both systems auditable"
|
|
||||||
|
|
||||||
scorecard:
|
|
||||||
pillars:
|
|
||||||
- name: "Security & Compliance"
|
|
||||||
status: "PASS"
|
|
||||||
passed: 24
|
|
||||||
total: 26
|
|
||||||
- name: "Reliability & Resilience"
|
|
||||||
status: "PASS_WITH_RECOMMENDATIONS"
|
|
||||||
passed: 7
|
|
||||||
total: 8
|
|
||||||
- name: "Performance & Cost"
|
|
||||||
status: "PASS"
|
|
||||||
passed: 5
|
|
||||||
total: 5
|
|
||||||
- name: "Operational Efficiency"
|
|
||||||
status: "PASS_WITH_RECOMMENDATIONS"
|
|
||||||
passed: 9
|
|
||||||
total: 10
|
|
||||||
- name: "Distributed Cloud"
|
|
||||||
status: "NOT_APPLICABLE"
|
|
||||||
passed: 0
|
|
||||||
total: 0
|
|
||||||
recommendations:
|
|
||||||
- "45/49 checks passed — strong architecture for regulated workload"
|
|
||||||
- "Add runbook for Data Guard failover testing (quarterly DR drill)"
|
|
||||||
- "Consider OCI Database Management for centralized fleet monitoring"
|
|
||||||
- "Define capacity growth plan for fiscal close peak periods"
|
|
||||||
|
|
||||||
next_steps:
|
|
||||||
steps:
|
|
||||||
- "Board approval for OCI migration budget — CFO — by 2026-05-15"
|
|
||||||
- "Provision OCI tenancy + ExaCS in Queretaro — Carlos Mendoza — by 2026-06-01"
|
|
||||||
- "Start FastConnect provisioning with partner — Network team — by 2026-06-01"
|
|
||||||
- "EBS customization inventory and Pre-Prod testing — DBA team — by 2026-06-15"
|
|
||||||
- "Schedule SOX audit coordination meeting — Compliance — by 2026-06-01"
|
|
||||||
contact_info: "Carlos Mendoza — Solutions Architect, Oracle Cloud Infrastructure"
|
|
||||||
Binary file not shown.
@@ -151,12 +151,13 @@ class TestKBLinter:
|
|||||||
# (like 'cli', 'maintenance-window', etc.)
|
# (like 'cli', 'maintenance-window', etc.)
|
||||||
|
|
||||||
def test_owner_check(self):
|
def test_owner_check(self):
|
||||||
"""Owner check returns issues for TBD owners."""
|
"""Owner check returns a list and matches the current owners config."""
|
||||||
issues = kb_linter.check_owners()
|
issues = kb_linter.check_owners()
|
||||||
assert isinstance(issues, list)
|
assert isinstance(issues, list)
|
||||||
# We know some owners are TBD
|
# Current config may be fully assigned; if issues exist they should be
|
||||||
tbd_issues = [i for i in issues if "TBD" in i]
|
# the explicit TBD-owner messages emitted by the linter.
|
||||||
assert len(tbd_issues) > 0, "Expected TBD owner issues"
|
for issue in issues:
|
||||||
|
assert "TBD" in issue
|
||||||
|
|
||||||
def test_freshness_check(self):
|
def test_freshness_check(self):
|
||||||
"""Freshness check returns a list of file freshness reports."""
|
"""Freshness check returns a list of file freshness reports."""
|
||||||
@@ -207,34 +208,44 @@ class TestKBCLI:
|
|||||||
# Service changelog tests
|
# Service changelog tests
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
class TestServiceChangelogs:
|
class TestServiceChangelogs:
|
||||||
"""Verify that service files have changelog sections."""
|
"""Verify that service files load correctly and validate changelogs when present."""
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
def service_files(self):
|
def service_files(self):
|
||||||
services_dir = KB_DIR / "services"
|
services_dir = KB_DIR / "services"
|
||||||
return list(services_dir.glob("*.yaml"))
|
return list(services_dir.glob("*.yaml"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_service_yaml(filepath):
|
||||||
|
merged = {}
|
||||||
|
with open(filepath, "r", encoding="utf-8") as fh:
|
||||||
|
for doc in yaml.safe_load_all(fh):
|
||||||
|
if isinstance(doc, dict):
|
||||||
|
merged.update(doc)
|
||||||
|
return merged
|
||||||
|
|
||||||
def test_service_files_exist(self, service_files):
|
def test_service_files_exist(self, service_files):
|
||||||
assert len(service_files) > 0
|
assert len(service_files) > 0
|
||||||
|
|
||||||
def test_service_files_have_changelog(self, service_files):
|
def test_service_files_load_as_merged_documents(self, service_files):
|
||||||
"""Each service file should have a changelog section."""
|
"""Service files may be multi-document YAML and should load via safe_load_all."""
|
||||||
for filepath in service_files:
|
for filepath in service_files:
|
||||||
with open(filepath, "r") as fh:
|
data = self._load_service_yaml(filepath)
|
||||||
data = yaml.safe_load(fh)
|
assert isinstance(data, dict), f"Service file '{filepath.name}' did not merge into a dict"
|
||||||
assert "changelog" in data, (
|
assert any(key in data for key in ("service", "description", "swagger_ui", "kb_target_file")), (
|
||||||
f"Service file '{filepath.name}' missing 'changelog' section"
|
f"Service file '{filepath.name}' is missing an identifying top-level key"
|
||||||
)
|
|
||||||
assert len(data["changelog"]) > 0, (
|
|
||||||
f"Service file '{filepath.name}' has empty changelog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_changelog_entries_have_required_fields(self, service_files):
|
def test_changelog_entries_have_required_fields(self, service_files):
|
||||||
"""Changelog entries must have date, contributor, change."""
|
"""Changelog entries must have date, contributor, change when a changelog exists."""
|
||||||
|
changelog_files = 0
|
||||||
for filepath in service_files:
|
for filepath in service_files:
|
||||||
with open(filepath, "r") as fh:
|
data = self._load_service_yaml(filepath)
|
||||||
data = yaml.safe_load(fh)
|
if "changelog" not in data:
|
||||||
|
continue
|
||||||
|
changelog_files += 1
|
||||||
for entry in data.get("changelog", []):
|
for entry in data.get("changelog", []):
|
||||||
assert "date" in entry, f"Changelog entry in {filepath.name} missing 'date'"
|
assert "date" in entry, f"Changelog entry in {filepath.name} missing 'date'"
|
||||||
assert "contributor" in entry, f"Changelog entry in {filepath.name} missing 'contributor'"
|
assert "contributor" in entry, f"Changelog entry in {filepath.name} missing 'contributor'"
|
||||||
assert "change" in entry, f"Changelog entry in {filepath.name} missing 'change'"
|
assert "change" in entry, f"Changelog entry in {filepath.name} missing 'change'"
|
||||||
|
assert changelog_files > 0, "Expected at least one service file with changelog entries"
|
||||||
|
|||||||
83
tests/test_oci_bizcase_gen_input_aliases.py
Normal file
83
tests/test_oci_bizcase_gen_input_aliases.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
TOOLS_DIR = PROJECT_ROOT / "tools"
|
||||||
|
|
||||||
|
if str(TOOLS_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(TOOLS_DIR))
|
||||||
|
|
||||||
|
from oci_bizcase_gen import BusinessCaseDeckGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_slide_text(gen):
|
||||||
|
texts = []
|
||||||
|
for slide in gen.prs.slides:
|
||||||
|
for shape in slide.shapes:
|
||||||
|
if hasattr(shape, "text"):
|
||||||
|
text = shape.text.strip()
|
||||||
|
if text:
|
||||||
|
texts.append(text)
|
||||||
|
if getattr(shape, "has_table", False):
|
||||||
|
for row in shape.table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
text = cell.text.strip()
|
||||||
|
if text:
|
||||||
|
texts.append(text)
|
||||||
|
return "\n".join(texts)
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_cover_aliases_and_recommendation_fallback():
|
||||||
|
spec = {
|
||||||
|
"customer": "Acme Corp",
|
||||||
|
"author": "Diego",
|
||||||
|
"generated_on": "2026-04-21",
|
||||||
|
"recommendation": {
|
||||||
|
"commitment_amount": "100K",
|
||||||
|
"commitment_type": "UCM",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = BusinessCaseDeckGenerator.from_spec(spec)
|
||||||
|
text = _collect_slide_text(gen)
|
||||||
|
|
||||||
|
assert "Acme Corp" in text
|
||||||
|
assert "Prepared by: Diego" in text
|
||||||
|
assert "Recommended: 100K UCM commitment" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_roadmap_aliases_and_mixed_next_steps():
|
||||||
|
spec = {
|
||||||
|
"customer_name": "Acme Corp",
|
||||||
|
"roadmap": {
|
||||||
|
"phases": [
|
||||||
|
{
|
||||||
|
"title": "Assess",
|
||||||
|
"weeks": "2 weeks",
|
||||||
|
"outputs": ["Blueprint"],
|
||||||
|
"milestones": ["Kickoff"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_duration": "6 weeks",
|
||||||
|
},
|
||||||
|
"recommendation": {
|
||||||
|
"summary": "Proceed with phased migration",
|
||||||
|
"next_steps": [
|
||||||
|
"Confirm scope",
|
||||||
|
{"action": "Approve budget", "owner": "Finance", "deadline": "May"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = BusinessCaseDeckGenerator.from_spec(spec)
|
||||||
|
text = _collect_slide_text(gen)
|
||||||
|
|
||||||
|
assert "Assess" in text
|
||||||
|
assert "2 weeks" in text
|
||||||
|
assert "Kickoff" in text
|
||||||
|
assert "Blueprint" in text
|
||||||
|
assert "Confirm scope" in text
|
||||||
|
assert "Approve budget" in text
|
||||||
|
assert "Finance" in text
|
||||||
|
assert "May" in text
|
||||||
80
tests/test_oci_bom_gen_input_aliases.py
Normal file
80
tests/test_oci_bom_gen_input_aliases.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
TOOLS_DIR = PROJECT_ROOT / "tools"
|
||||||
|
|
||||||
|
if str(TOOLS_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(TOOLS_DIR))
|
||||||
|
|
||||||
|
from oci_bom_gen import OCIBomGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_metadata_and_line_item_aliases():
|
||||||
|
spec = {
|
||||||
|
"customer": "Acme Corp",
|
||||||
|
"project": "Database Migration",
|
||||||
|
"author": "Diego",
|
||||||
|
"reference": "REF-1",
|
||||||
|
"conversion": {
|
||||||
|
"currency": "BRL",
|
||||||
|
"exchange_rate": 5.2,
|
||||||
|
"tax_rate": 0.1,
|
||||||
|
},
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"part_number": "SKU-001",
|
||||||
|
"quantity": 2,
|
||||||
|
"units": 730,
|
||||||
|
"discount_pct": 15,
|
||||||
|
"label": "Database",
|
||||||
|
"note": "Primary environment",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = OCIBomGenerator.from_spec(spec)
|
||||||
|
item = gen.line_items[0]
|
||||||
|
|
||||||
|
assert gen.customer == "Acme Corp"
|
||||||
|
assert gen.project == "Database Migration"
|
||||||
|
assert gen.prepared_by == "Diego"
|
||||||
|
assert gen.reference_label == "REF-1"
|
||||||
|
assert gen.conversion["target_currency"] == "BRL"
|
||||||
|
assert item["sku"] == "SKU-001"
|
||||||
|
assert item["qty"] == 2
|
||||||
|
assert item["hours_units"] == 730
|
||||||
|
assert item["discount"] == 15
|
||||||
|
assert item["custom_label"] == "Database"
|
||||||
|
assert item["custom_note"] == "Primary environment"
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_canonical_sku_and_custom_note():
|
||||||
|
spec = {
|
||||||
|
"line_items": [
|
||||||
|
{
|
||||||
|
"sku": "SKU-002",
|
||||||
|
"qty": 1,
|
||||||
|
"hours_units": 100,
|
||||||
|
"custom_note": "Canonical form",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = OCIBomGenerator.from_spec(spec)
|
||||||
|
item = gen.line_items[0]
|
||||||
|
|
||||||
|
assert item["sku"] == "SKU-002"
|
||||||
|
assert item["qty"] == 1
|
||||||
|
assert item["hours_units"] == 100
|
||||||
|
assert item["custom_note"] == "Canonical form"
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_requires_sku_or_part_number():
|
||||||
|
spec = {"line_items": [{"quantity": 1}]}
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="sku"):
|
||||||
|
OCIBomGenerator.from_spec(spec)
|
||||||
98
tests/test_oci_deck_gen_input_aliases.py
Normal file
98
tests/test_oci_deck_gen_input_aliases.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
TOOLS_DIR = PROJECT_ROOT / "tools"
|
||||||
|
|
||||||
|
if str(TOOLS_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(TOOLS_DIR))
|
||||||
|
|
||||||
|
from oci_deck_gen import OCIDeckGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_slide_text(gen):
|
||||||
|
texts = []
|
||||||
|
for slide in gen.prs.slides:
|
||||||
|
for shape in slide.shapes:
|
||||||
|
if hasattr(shape, "text"):
|
||||||
|
text = shape.text.strip()
|
||||||
|
if text:
|
||||||
|
texts.append(text)
|
||||||
|
if getattr(shape, "has_table", False):
|
||||||
|
for row in shape.table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
text = cell.text.strip()
|
||||||
|
if text:
|
||||||
|
texts.append(text)
|
||||||
|
return "\n".join(texts)
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_metadata_aliases_and_current_state_string():
|
||||||
|
spec = {
|
||||||
|
"metadata": {
|
||||||
|
"customer_name": "Acme Corp",
|
||||||
|
"project_name": "DB Migration",
|
||||||
|
"prepared_by": "Diego",
|
||||||
|
"title": "Target Architecture",
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"why": "Modernize platform",
|
||||||
|
"current_state": "Legacy estate",
|
||||||
|
"target_state": "OCI landing zone",
|
||||||
|
"timeline": "Q3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = OCIDeckGenerator.from_spec(spec)
|
||||||
|
text = _collect_slide_text(gen)
|
||||||
|
|
||||||
|
assert gen.customer == "Acme Corp"
|
||||||
|
assert gen.project == "DB Migration"
|
||||||
|
assert gen.architect == "Diego"
|
||||||
|
assert "Legacy estate" in text
|
||||||
|
assert "OCI landing zone" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_aliases_for_cost_migration_risk_scorecard_and_next_steps():
|
||||||
|
spec = {
|
||||||
|
"metadata": {"customer": "Acme"},
|
||||||
|
"cost": {
|
||||||
|
"line_items": [
|
||||||
|
{"name": "DB Service", "monthly": "USD 100", "note": "Includes storage"}
|
||||||
|
],
|
||||||
|
"assumptions": ["1 region"],
|
||||||
|
"show_byol": False,
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"phases": [
|
||||||
|
{"title": "Discover", "weeks": "2 weeks", "tasks": ["Inventory", "Assess"]}
|
||||||
|
],
|
||||||
|
"tools": ["ZDM"],
|
||||||
|
"downtime": "Near-zero",
|
||||||
|
},
|
||||||
|
"risks": [
|
||||||
|
{"title": "Cutover risk", "action": "Pilot first", "severity": "high"}
|
||||||
|
],
|
||||||
|
"scorecard": {
|
||||||
|
"pillars": [
|
||||||
|
{"pillar": "Security", "status": "PASS", "passed": 2, "total": 2}
|
||||||
|
],
|
||||||
|
"recommendations": ["Enable auditing"],
|
||||||
|
},
|
||||||
|
"next_steps": {"next_steps": ["Validate scope"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = OCIDeckGenerator.from_spec(spec)
|
||||||
|
text = _collect_slide_text(gen)
|
||||||
|
|
||||||
|
assert "DB Service" in text
|
||||||
|
assert "USD 100" in text
|
||||||
|
assert "Includes storage" in text
|
||||||
|
assert "Discover" in text
|
||||||
|
assert "Inventory" in text
|
||||||
|
assert "Cutover risk" in text
|
||||||
|
assert "Pilot first" in text
|
||||||
|
assert "Security" in text
|
||||||
|
assert "2/2" in text
|
||||||
|
assert "Validate scope" in text
|
||||||
76
tests/test_oci_diagram_gen_input_aliases.py
Normal file
76
tests/test_oci_diagram_gen_input_aliases.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
TOOLS_DIR = PROJECT_ROOT / "tools"
|
||||||
|
|
||||||
|
if str(TOOLS_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(TOOLS_DIR))
|
||||||
|
|
||||||
|
from oci_diagram_gen import OCIDiagramGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_name_aliases_and_generates_ids():
|
||||||
|
spec = {
|
||||||
|
"external": [{"name": "Users"}],
|
||||||
|
"clouds": [{"name": "AWS", "services": [{"name": "EC2 App", "service_type": "ec2"}]}],
|
||||||
|
"tenancy": {
|
||||||
|
"regions": [
|
||||||
|
{
|
||||||
|
"name": "Ashburn",
|
||||||
|
"vcns": [
|
||||||
|
{
|
||||||
|
"name": "App VCN",
|
||||||
|
"subnets": [
|
||||||
|
{"name": "App Subnet", "services": [{"name": "App VM"}]}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"on_prem": {"name": "Primary DC", "services": [{"name": "Legacy DB"}]},
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = OCIDiagramGenerator.from_spec(spec)
|
||||||
|
values = [getattr(obj, "value", "") for obj in gen._objects.values()]
|
||||||
|
|
||||||
|
assert "Users" in values
|
||||||
|
assert "AWS" in values
|
||||||
|
assert "Ashburn" in values
|
||||||
|
assert "VCN App VCN" in values
|
||||||
|
assert "App Subnet" in values
|
||||||
|
assert "App VM" in values
|
||||||
|
assert "Primary DC" in values
|
||||||
|
assert "Legacy DB" in values
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_spec_accepts_source_target_and_omits_unresolved_connections():
|
||||||
|
spec = {
|
||||||
|
"external": [{"name": "Users"}],
|
||||||
|
"tenancy": {
|
||||||
|
"regions": [
|
||||||
|
{
|
||||||
|
"name": "Ashburn",
|
||||||
|
"vcns": [
|
||||||
|
{
|
||||||
|
"name": "App VCN",
|
||||||
|
"subnets": [
|
||||||
|
{"name": "App Subnet", "services": [{"name": "App VM"}]}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": [
|
||||||
|
{"source": "Users", "target": "App VM", "type": "standard", "label": "HTTPS"},
|
||||||
|
{"from": "Users", "to": "App VM", "type": "standard", "label": "Duplicate"},
|
||||||
|
{"source": "Users", "target": "Missing VM", "type": "standard", "label": "Broken"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
gen = OCIDiagramGenerator.from_spec(spec)
|
||||||
|
|
||||||
|
assert gen._connection_count == 1
|
||||||
@@ -28,6 +28,37 @@ from pptx.enum.shapes import MSO_SHAPE
|
|||||||
from oci_pptx_base import Colors, Layouts, OraclePresBase
|
from oci_pptx_base import Colors, Layouts, OraclePresBase
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Defensive input helpers
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def pick(mapping: dict, *keys, default=""):
|
||||||
|
"""Return the first non-empty value for any of the provided keys."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return default
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
value = mapping.get(key)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def pick_list(mapping: dict, *keys):
|
||||||
|
"""Return the first list-like field normalized to a list."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return []
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
value = mapping.get(key)
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
return [value]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Business Case Deck Generator
|
# Business Case Deck Generator
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -461,10 +492,10 @@ class BusinessCaseDeckGenerator(OraclePresBase):
|
|||||||
for i, phase in enumerate(phases[:4]):
|
for i, phase in enumerate(phases[:4]):
|
||||||
color = phase_colors[i % len(phase_colors)]
|
color = phase_colors[i % len(phase_colors)]
|
||||||
y = content_start + i * row_h
|
y = content_start + i * row_h
|
||||||
name = phase.get("name", f"Phase {i+1}")
|
name = pick(phase, "name", "title", default=f"Phase {i+1}")
|
||||||
duration = phase.get("duration", "")
|
duration = pick(phase, "duration", "weeks")
|
||||||
deliverables = phase.get("deliverables", [])
|
deliverables = pick_list(phase, "deliverables", "outputs")
|
||||||
quick_wins = phase.get("quick_wins", [])
|
quick_wins = pick_list(phase, "quick_wins", "milestones")
|
||||||
|
|
||||||
# Alternating row background
|
# Alternating row background
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
@@ -593,10 +624,10 @@ class BusinessCaseDeckGenerator(OraclePresBase):
|
|||||||
|
|
||||||
# Slide 1: Cover
|
# Slide 1: Cover
|
||||||
gen.add_cover_slide(
|
gen.add_cover_slide(
|
||||||
customer=bc.get("customer_name", ""),
|
customer=pick(bc, "customer_name", "customer"),
|
||||||
subtitle="Business Case for Oracle Cloud Infrastructure",
|
subtitle="Business Case for Oracle Cloud Infrastructure",
|
||||||
prepared_by=bc.get("prepared_by", ""),
|
prepared_by=pick(bc, "prepared_by", "author"),
|
||||||
date=bc.get("date", ""),
|
date=pick(bc, "date", "generated_on"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slide 2: Executive Summary
|
# Slide 2: Executive Summary
|
||||||
@@ -608,18 +639,21 @@ class BusinessCaseDeckGenerator(OraclePresBase):
|
|||||||
drivers_data = bc.get("drivers", {})
|
drivers_data = bc.get("drivers", {})
|
||||||
if drivers_data:
|
if drivers_data:
|
||||||
driver_statements = []
|
driver_statements = []
|
||||||
primary = drivers_data.get("primary", "")
|
primary = pick(drivers_data, "primary")
|
||||||
urgency = drivers_data.get("urgency", "")
|
urgency = pick(drivers_data, "urgency")
|
||||||
coi = drivers_data.get("cost_of_inaction", {})
|
coi = drivers_data.get("cost_of_inaction", {})
|
||||||
|
|
||||||
if primary:
|
if primary:
|
||||||
driver_statements.append(f"Primary Driver: {primary.replace('_', ' ').title()}\n{urgency}" if urgency else primary.replace('_', ' ').title())
|
driver_statements.append(f"Primary Driver: {primary.replace('_', ' ').title()}\n{urgency}" if urgency else primary.replace('_', ' ').title())
|
||||||
if coi.get("financial"):
|
financial = pick(coi, "financial")
|
||||||
driver_statements.append(f"Financial Impact of Inaction\n{coi['financial']}")
|
operational = pick(coi, "operational")
|
||||||
if coi.get("operational"):
|
strategic = pick(coi, "strategic")
|
||||||
driver_statements.append(f"Operational Impact\n{coi['operational']}")
|
if financial:
|
||||||
elif coi.get("strategic"):
|
driver_statements.append(f"Financial Impact of Inaction\n{financial}")
|
||||||
driver_statements.append(f"Strategic Risk\n{coi['strategic']}")
|
if operational:
|
||||||
|
driver_statements.append(f"Operational Impact\n{operational}")
|
||||||
|
elif strategic:
|
||||||
|
driver_statements.append(f"Strategic Risk\n{strategic}")
|
||||||
|
|
||||||
if driver_statements:
|
if driver_statements:
|
||||||
gen.add_business_drivers_slide(driver_statements)
|
gen.add_business_drivers_slide(driver_statements)
|
||||||
@@ -641,8 +675,8 @@ class BusinessCaseDeckGenerator(OraclePresBase):
|
|||||||
|
|
||||||
# Slide 7: Risk Assessment
|
# Slide 7: Risk Assessment
|
||||||
risks = bc.get("risks", {})
|
risks = bc.get("risks", {})
|
||||||
migration_risks = risks.get("migration_risks", [])
|
migration_risks = pick_list(risks, "migration_risks")
|
||||||
do_nothing_risks = risks.get("do_nothing_risks", [])
|
do_nothing_risks = pick_list(risks, "do_nothing_risks")
|
||||||
if migration_risks or do_nothing_risks:
|
if migration_risks or do_nothing_risks:
|
||||||
gen.add_risk_slide(migration_risks, do_nothing_risks)
|
gen.add_risk_slide(migration_risks, do_nothing_risks)
|
||||||
|
|
||||||
@@ -650,24 +684,24 @@ class BusinessCaseDeckGenerator(OraclePresBase):
|
|||||||
roadmap = bc.get("roadmap", {})
|
roadmap = bc.get("roadmap", {})
|
||||||
if roadmap.get("phases"):
|
if roadmap.get("phases"):
|
||||||
gen.add_roadmap_slide(
|
gen.add_roadmap_slide(
|
||||||
phases=roadmap["phases"],
|
phases=pick_list(roadmap, "phases"),
|
||||||
total_duration=roadmap.get("total_duration", ""),
|
total_duration=pick(roadmap, "total_duration"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slide 9: Recommendation
|
# Slide 9: Recommendation
|
||||||
rec = bc.get("recommendation", {})
|
rec = bc.get("recommendation", {})
|
||||||
if rec:
|
if rec:
|
||||||
summary = rec.get("summary", "")
|
summary = pick(rec, "summary")
|
||||||
if not summary and rec.get("commitment_amount"):
|
if not summary and rec.get("commitment_amount"):
|
||||||
summary = f"Recommended: {rec['commitment_amount']} {rec.get('commitment_type', 'UCM')} commitment"
|
summary = f"Recommended: {rec['commitment_amount']} {rec.get('commitment_type', 'UCM')} commitment"
|
||||||
if summary:
|
if summary:
|
||||||
gen.add_recommendation_slide(
|
gen.add_recommendation_slide(
|
||||||
summary=summary,
|
summary=summary,
|
||||||
next_steps=rec.get("next_steps", []),
|
next_steps=pick_list(rec, "next_steps"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Closing slides
|
# Closing slides
|
||||||
prepared_by = bc.get("prepared_by", "")
|
prepared_by = pick(bc, "prepared_by", "author")
|
||||||
gen.add_closing_slide(name=prepared_by)
|
gen.add_closing_slide(name=prepared_by)
|
||||||
|
|
||||||
return gen
|
return gen
|
||||||
|
|||||||
@@ -73,6 +73,18 @@ TAIL_COLS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def pick(mapping: dict, *keys, default=""):
|
||||||
|
"""Return the first non-empty value for any of the provided keys."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return default
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
value = mapping.get(key)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
class OCIBomGenerator:
|
class OCIBomGenerator:
|
||||||
"""Generate OCI Bill of Materials (.xlsx) with Oracle Redwood styling."""
|
"""Generate OCI Bill of Materials (.xlsx) with Oracle Redwood styling."""
|
||||||
|
|
||||||
@@ -667,17 +679,22 @@ class OCIBomGenerator:
|
|||||||
def from_spec(cls, spec: dict, catalog_path: Optional[str] = None) -> "OCIBomGenerator":
|
def from_spec(cls, spec: dict, catalog_path: Optional[str] = None) -> "OCIBomGenerator":
|
||||||
"""Build BOM from a YAML specification dict."""
|
"""Build BOM from a YAML specification dict."""
|
||||||
bom = spec.get("bom", spec)
|
bom = spec.get("bom", spec)
|
||||||
|
conversion = bom.get("conversion")
|
||||||
|
if isinstance(conversion, dict):
|
||||||
|
conversion = dict(conversion)
|
||||||
|
if "target_currency" not in conversion and "currency" in conversion:
|
||||||
|
conversion["target_currency"] = conversion.get("currency")
|
||||||
|
|
||||||
gen = cls(
|
gen = cls(
|
||||||
customer=bom.get("customer_name", ""),
|
customer=pick(bom, "customer_name", "customer"),
|
||||||
project=bom.get("project_name", ""),
|
project=pick(bom, "project_name", "project"),
|
||||||
prepared_by=bom.get("prepared_by", ""),
|
prepared_by=pick(bom, "prepared_by", "author"),
|
||||||
bom_date=bom.get("date", ""),
|
bom_date=bom.get("date", ""),
|
||||||
reference_label=bom.get("reference_label", ""),
|
reference_label=pick(bom, "reference_label", "reference"),
|
||||||
currency=bom.get("currency", "USD"),
|
currency=bom.get("currency", "USD"),
|
||||||
realm=bom.get("realm", ""),
|
realm=bom.get("realm", ""),
|
||||||
service_type=bom.get("service_type", ""),
|
service_type=bom.get("service_type", ""),
|
||||||
conversion=bom.get("conversion"),
|
conversion=conversion,
|
||||||
notes=bom.get("notes"),
|
notes=bom.get("notes"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -688,14 +705,17 @@ class OCIBomGenerator:
|
|||||||
global_discount = bom_meta.get("discount_pct", bom.get("discount_pct", 0.0))
|
global_discount = bom_meta.get("discount_pct", bom.get("discount_pct", 0.0))
|
||||||
|
|
||||||
for item in bom.get("line_items", []):
|
for item in bom.get("line_items", []):
|
||||||
|
sku = pick(item, "sku", "part_number")
|
||||||
|
if sku in ("", None):
|
||||||
|
raise ValueError("BOM line item requires 'sku' or 'part_number'")
|
||||||
gen.add_line_item(
|
gen.add_line_item(
|
||||||
sku=str(item["sku"]),
|
sku=str(sku),
|
||||||
qty=item.get("qty", 0),
|
qty=pick(item, "qty", "quantity", default=0),
|
||||||
hours_units=item.get("hours_units"),
|
hours_units=pick(item, "hours_units", "units", "hours", default=None),
|
||||||
months=item.get("months", 12),
|
months=item.get("months", 12),
|
||||||
discount=item.get("discount", global_discount),
|
discount=pick(item, "discount", "discount_pct", default=global_discount),
|
||||||
custom_label=item.get("custom_label", ""),
|
custom_label=pick(item, "custom_label", "label"),
|
||||||
custom_note=item.get("custom_note", ""),
|
custom_note=pick(item, "custom_note", "note", "notes"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return gen
|
return gen
|
||||||
|
|||||||
@@ -29,6 +29,45 @@ from pptx.enum.shapes import MSO_SHAPE
|
|||||||
from oci_pptx_base import Colors, Layouts, OraclePresBase
|
from oci_pptx_base import Colors, Layouts, OraclePresBase
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Defensive input helpers
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def pick(mapping: dict, *keys, default=""):
|
||||||
|
"""Return the first non-empty value for any of the provided keys."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return default
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
value = mapping.get(key)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def coerce_list(value):
|
||||||
|
"""Accept list, string, tuple, or None and normalize to a list."""
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
if isinstance(value, tuple):
|
||||||
|
return list(value)
|
||||||
|
if isinstance(value, str):
|
||||||
|
return [value] if value else []
|
||||||
|
return [value]
|
||||||
|
|
||||||
|
|
||||||
|
def pick_list(mapping: dict, *keys):
|
||||||
|
"""Return the first list-like field normalized to a list."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return []
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
return coerce_list(mapping.get(key))
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# MCP flat-spec adapter
|
# MCP flat-spec adapter
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -525,8 +564,9 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
for i, wl in enumerate(workloads):
|
for i, wl in enumerate(workloads):
|
||||||
row_idx = i + 1
|
row_idx = i + 1
|
||||||
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
||||||
self._style_table_cell(table.cell(row_idx, 0), wl["name"], font_size=10, bold=True, bg_color=bg)
|
workload_name = pick(wl, "name", "workload", "title")
|
||||||
tier_label = wl.get("tier", "Silver")
|
self._style_table_cell(table.cell(row_idx, 0), workload_name, font_size=10, bold=True, bg_color=bg)
|
||||||
|
tier_label = pick(wl, "tier", "name", default="Silver")
|
||||||
tier_color = tier_colors.get(tier_label.lower(), Colors.SECONDARY_TEXT)
|
tier_color = tier_colors.get(tier_label.lower(), Colors.SECONDARY_TEXT)
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 1), tier_label.title(),
|
table.cell(row_idx, 1), tier_label.title(),
|
||||||
@@ -613,11 +653,11 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
row_idx = i + 1
|
row_idx = i + 1
|
||||||
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 0), dec["decision"],
|
table.cell(row_idx, 0), pick(dec, "decision", "title"),
|
||||||
font_size=11, bold=True, bg_color=bg,
|
font_size=11, bold=True, bg_color=bg,
|
||||||
)
|
)
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 1), dec["rationale"],
|
table.cell(row_idx, 1), pick(dec, "rationale", "reason", "notes"),
|
||||||
font_size=10, bg_color=bg,
|
font_size=10, bg_color=bg,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -659,10 +699,10 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
for i, tier in enumerate(tiers):
|
for i, tier in enumerate(tiers):
|
||||||
row_idx = i + 1
|
row_idx = i + 1
|
||||||
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
||||||
self._style_table_cell(table.cell(row_idx, 0), tier["tier"], font_size=10, bold=True, bg_color=bg)
|
self._style_table_cell(table.cell(row_idx, 0), pick(tier, "tier", "name"), font_size=10, bold=True, bg_color=bg)
|
||||||
self._style_table_cell(table.cell(row_idx, 1), tier["technology"], font_size=10, bg_color=bg)
|
self._style_table_cell(table.cell(row_idx, 1), pick(tier, "technology", "architecture", "pattern"), font_size=10, bg_color=bg)
|
||||||
self._style_table_cell(table.cell(row_idx, 2), tier["rto"], font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER)
|
self._style_table_cell(table.cell(row_idx, 2), pick(tier, "rto"), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER)
|
||||||
self._style_table_cell(table.cell(row_idx, 3), tier["rpo"], font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER)
|
self._style_table_cell(table.cell(row_idx, 3), pick(tier, "rpo"), font_size=10, bg_color=bg, alignment=PP_ALIGN.CENTER)
|
||||||
|
|
||||||
def add_security_slide(self, controls: dict, compliance: list = None):
|
def add_security_slide(self, controls: dict, compliance: list = None):
|
||||||
"""Slide 6: Security & Compliance.
|
"""Slide 6: Security & Compliance.
|
||||||
@@ -850,19 +890,22 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
|
|
||||||
for i, item in enumerate(line_items):
|
for i, item in enumerate(line_items):
|
||||||
row_idx = i + 1
|
row_idx = i + 1
|
||||||
is_total = "total" in item.get("component", "").lower()
|
component = pick(item, "component", "name", "label")
|
||||||
|
notes = pick(item, "notes", "note")
|
||||||
|
monthly_payg = pick(item, "monthly_payg", "monthly")
|
||||||
|
is_total = "total" in str(component).lower()
|
||||||
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 and not is_total else None
|
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 and not is_total else None
|
||||||
if is_total:
|
if is_total:
|
||||||
bg = Colors.TEAL
|
bg = Colors.TEAL
|
||||||
|
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 0), item["component"],
|
table.cell(row_idx, 0), component,
|
||||||
font_size=10, bold=is_total,
|
font_size=10, bold=is_total,
|
||||||
color=Colors.WHITE if is_total else None,
|
color=Colors.WHITE if is_total else None,
|
||||||
bg_color=bg,
|
bg_color=bg,
|
||||||
)
|
)
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 1), item.get("monthly_payg", ""),
|
table.cell(row_idx, 1), monthly_payg,
|
||||||
font_size=10, bold=is_total,
|
font_size=10, bold=is_total,
|
||||||
color=Colors.WHITE if is_total else None,
|
color=Colors.WHITE if is_total else None,
|
||||||
bg_color=bg, alignment=PP_ALIGN.RIGHT,
|
bg_color=bg, alignment=PP_ALIGN.RIGHT,
|
||||||
@@ -875,13 +918,13 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
bg_color=bg, alignment=PP_ALIGN.RIGHT,
|
bg_color=bg, alignment=PP_ALIGN.RIGHT,
|
||||||
)
|
)
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 3), item.get("notes", ""),
|
table.cell(row_idx, 3), notes,
|
||||||
font_size=9, bg_color=bg,
|
font_size=9, bg_color=bg,
|
||||||
color=Colors.WHITE if is_total else Colors.SECONDARY_TEXT,
|
color=Colors.WHITE if is_total else Colors.SECONDARY_TEXT,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 2), item.get("notes", ""),
|
table.cell(row_idx, 2), notes,
|
||||||
font_size=9, bg_color=bg,
|
font_size=9, bg_color=bg,
|
||||||
color=Colors.WHITE if is_total else Colors.SECONDARY_TEXT,
|
color=Colors.WHITE if is_total else Colors.SECONDARY_TEXT,
|
||||||
)
|
)
|
||||||
@@ -983,14 +1026,14 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
|
|
||||||
for i, phase in enumerate(phases):
|
for i, phase in enumerate(phases):
|
||||||
color = phase_colors[i % len(phase_colors)]
|
color = phase_colors[i % len(phase_colors)]
|
||||||
duration = phase.get("duration", phase.get("weeks", ""))
|
duration = pick(phase, "duration", "weeks")
|
||||||
milestones = phase.get("milestones", phase.get("tasks", []))
|
milestones = pick_list(phase, "milestones", "tasks")
|
||||||
|
|
||||||
# Phase label (left of bar)
|
# Phase label (left of bar)
|
||||||
self._add_textbox(
|
self._add_textbox(
|
||||||
slide, self.MARGIN, y,
|
slide, self.MARGIN, y,
|
||||||
Inches(2.3), Inches(0.4),
|
Inches(2.3), Inches(0.4),
|
||||||
text=phase["name"], font_size=11, bold=True,
|
text=pick(phase, "name", "title"), font_size=11, bold=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Colored phase bar with duration
|
# Colored phase bar with duration
|
||||||
@@ -1122,7 +1165,7 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
row_idx = i + 1
|
row_idx = i + 1
|
||||||
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
bg = Colors.TABLE_ALT_ROW if row_idx % 2 == 0 else None
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 0), risk["risk"],
|
table.cell(row_idx, 0), pick(risk, "risk", "title"),
|
||||||
font_size=10, bg_color=bg,
|
font_size=10, bg_color=bg,
|
||||||
)
|
)
|
||||||
sev = risk.get("severity", "MEDIUM").upper()
|
sev = risk.get("severity", "MEDIUM").upper()
|
||||||
@@ -1133,7 +1176,7 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
bg_color=bg, alignment=PP_ALIGN.CENTER,
|
bg_color=bg, alignment=PP_ALIGN.CENTER,
|
||||||
)
|
)
|
||||||
self._style_table_cell(
|
self._style_table_cell(
|
||||||
table.cell(row_idx, 2), risk["mitigation"],
|
table.cell(row_idx, 2), pick(risk, "mitigation", "action", "response"),
|
||||||
font_size=10, bg_color=bg,
|
font_size=10, bg_color=bg,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1155,7 +1198,8 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
|
|
||||||
y = Inches(1.4)
|
y = Inches(1.4)
|
||||||
for pillar in pillars:
|
for pillar in pillars:
|
||||||
style = status_styles.get(pillar["status"], status_styles["PASS"])
|
status = pick(pillar, "status", default="PASS")
|
||||||
|
style = status_styles.get(status, status_styles["PASS"])
|
||||||
|
|
||||||
# Status indicator pill
|
# Status indicator pill
|
||||||
pill = self._add_rect(
|
pill = self._add_rect(
|
||||||
@@ -1173,11 +1217,13 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
self._add_textbox(
|
self._add_textbox(
|
||||||
slide, Inches(1.2), y,
|
slide, Inches(1.2), y,
|
||||||
Inches(4), Inches(0.45),
|
Inches(4), Inches(0.45),
|
||||||
text=pillar["name"], font_size=13, bold=True,
|
text=pick(pillar, "name", "pillar"), font_size=13, bold=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Score
|
# Score
|
||||||
score_text = f"{pillar['passed']}/{pillar['total']}" if pillar['total'] > 0 else "—"
|
passed = pick(pillar, "passed", default=0)
|
||||||
|
total = pick(pillar, "total", default=0)
|
||||||
|
score_text = f"{passed}/{total}" if total > 0 else "—"
|
||||||
self._add_textbox(
|
self._add_textbox(
|
||||||
slide, Inches(5.5), y,
|
slide, Inches(5.5), y,
|
||||||
Inches(1.5), Inches(0.45),
|
Inches(1.5), Inches(0.45),
|
||||||
@@ -1274,24 +1320,24 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
|
|
||||||
meta = spec.get("metadata", {})
|
meta = spec.get("metadata", {})
|
||||||
gen = cls(
|
gen = cls(
|
||||||
customer=meta.get("customer", ""),
|
customer=pick(meta, "customer", "customer_name"),
|
||||||
project=meta.get("project", ""),
|
project=pick(meta, "project", "project_name"),
|
||||||
architect=meta.get("architect", ""),
|
architect=pick(meta, "architect", "prepared_by"),
|
||||||
firm=meta.get("firm", ""),
|
firm=meta.get("firm", ""),
|
||||||
template=template,
|
template=template,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slide 1: Title
|
# Slide 1: Title
|
||||||
gen.add_title_slide(subtitle=meta.get("subtitle", ""))
|
gen.add_title_slide(subtitle=pick(meta, "subtitle", "title"))
|
||||||
|
|
||||||
# Slide 2: Summary
|
# Slide 2: Summary
|
||||||
if "summary" in spec:
|
if "summary" in spec:
|
||||||
s = spec["summary"]
|
s = spec["summary"]
|
||||||
gen.add_summary_slide(
|
gen.add_summary_slide(
|
||||||
why=s.get("why", ""),
|
why=pick(s, "why"),
|
||||||
current_state=s.get("current_state", []),
|
current_state=coerce_list(s.get("current_state")),
|
||||||
target_state=s.get("target_state", ""),
|
target_state=pick(s, "target_state"),
|
||||||
timeline=s.get("timeline", ""),
|
timeline=pick(s, "timeline"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slide 3: Service Tiering (ECAL)
|
# Slide 3: Service Tiering (ECAL)
|
||||||
@@ -1343,8 +1389,8 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
if "cost" in spec:
|
if "cost" in spec:
|
||||||
c = spec["cost"]
|
c = spec["cost"]
|
||||||
gen.add_cost_slide(
|
gen.add_cost_slide(
|
||||||
line_items=c.get("line_items", []),
|
line_items=pick_list(c, "line_items"),
|
||||||
assumptions=c.get("assumptions", []),
|
assumptions=pick_list(c, "assumptions"),
|
||||||
show_byol=c.get("show_byol", True),
|
show_byol=c.get("show_byol", True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1361,9 +1407,9 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
if "migration" in spec:
|
if "migration" in spec:
|
||||||
m = spec["migration"]
|
m = spec["migration"]
|
||||||
gen.add_migration_slide(
|
gen.add_migration_slide(
|
||||||
phases=m.get("phases", []),
|
phases=pick_list(m, "phases"),
|
||||||
tools=m.get("tools", []),
|
tools=pick_list(m, "tools"),
|
||||||
downtime=m.get("downtime", ""),
|
downtime=pick(m, "downtime"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slide 13: Operational RACI (ECAL)
|
# Slide 13: Operational RACI (ECAL)
|
||||||
@@ -1382,16 +1428,16 @@ class OCIDeckGenerator(OraclePresBase):
|
|||||||
if "scorecard" in spec:
|
if "scorecard" in spec:
|
||||||
sc = spec["scorecard"]
|
sc = spec["scorecard"]
|
||||||
gen.add_scorecard_slide(
|
gen.add_scorecard_slide(
|
||||||
pillars=sc.get("pillars", []),
|
pillars=pick_list(sc, "pillars"),
|
||||||
recommendations=sc.get("recommendations", []),
|
recommendations=pick_list(sc, "recommendations"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slide 16: Next Steps
|
# Slide 16: Next Steps
|
||||||
if "next_steps" in spec:
|
if "next_steps" in spec:
|
||||||
ns = spec["next_steps"]
|
ns = spec["next_steps"]
|
||||||
gen.add_next_steps_slide(
|
gen.add_next_steps_slide(
|
||||||
steps=ns.get("steps", []),
|
steps=pick_list(ns, "steps", "next_steps"),
|
||||||
contact_info=ns.get("contact_info", ""),
|
contact_info=pick(ns, "contact_info"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Closing slides
|
# Closing slides
|
||||||
|
|||||||
@@ -408,6 +408,220 @@ OBS_TYPE_MAP = {
|
|||||||
"auditing": "monitoring", # uses monitoring icon as closest match
|
"auditing": "monitoring", # uses monitoring icon as closest match
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pick(mapping: dict, *keys, default=""):
|
||||||
|
"""Return the first non-empty value for any of the provided keys."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return default
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
value = mapping.get(key)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def pick_list(mapping: dict, *keys):
|
||||||
|
"""Return the first list-like field normalized to a list."""
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return []
|
||||||
|
for key in keys:
|
||||||
|
if key in mapping:
|
||||||
|
value = mapping.get(key)
|
||||||
|
if value is None:
|
||||||
|
return []
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
return [value]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _slugify(value: str) -> str:
|
||||||
|
text = re.sub(r"[^a-z0-9]+", "-", str(value or "").lower()).strip("-")
|
||||||
|
return text or "item"
|
||||||
|
|
||||||
|
|
||||||
|
class _SpecIdGenerator:
|
||||||
|
"""Generate stable unique ids within one normalized spec."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.used = set()
|
||||||
|
self.counters = {}
|
||||||
|
|
||||||
|
def make(self, prefix: str, seed: str = "") -> str:
|
||||||
|
base = f"{prefix}-{_slugify(seed)}" if seed else prefix
|
||||||
|
candidate = base
|
||||||
|
idx = 2
|
||||||
|
while candidate in self.used:
|
||||||
|
candidate = f"{base}-{idx}"
|
||||||
|
idx += 1
|
||||||
|
self.used.add(candidate)
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_diagram_spec(spec: dict) -> dict:
|
||||||
|
"""Normalize aliases and synthesize ids before rendering."""
|
||||||
|
spec = spec or {}
|
||||||
|
id_gen = _SpecIdGenerator()
|
||||||
|
aliases = {}
|
||||||
|
|
||||||
|
def remember(node: dict, node_id: str):
|
||||||
|
for key in ("id", "name", "label", "title"):
|
||||||
|
value = node.get(key)
|
||||||
|
if value:
|
||||||
|
aliases[str(value)] = node_id
|
||||||
|
|
||||||
|
def resolve_ref(value):
|
||||||
|
if value in (None, ""):
|
||||||
|
return None
|
||||||
|
return aliases.get(str(value), str(value))
|
||||||
|
|
||||||
|
def normalize_service(node: dict, prefix: str, default_type: str = "compute") -> dict:
|
||||||
|
raw = dict(node or {})
|
||||||
|
label = pick(raw, "label", "name", "title")
|
||||||
|
node_id = pick(raw, "id", "name") or id_gen.make(prefix, label)
|
||||||
|
normalized = dict(raw)
|
||||||
|
normalized["id"] = str(node_id)
|
||||||
|
normalized["label"] = label or str(node_id)
|
||||||
|
normalized["type"] = pick(raw, "type", "service_type", default=default_type)
|
||||||
|
remember(raw, normalized["id"])
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
normalized = {
|
||||||
|
"title": spec.get("title", ""),
|
||||||
|
"external": [],
|
||||||
|
"clouds": [],
|
||||||
|
"tenancy": {},
|
||||||
|
"connections": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for ext in pick_list(spec, "external"):
|
||||||
|
normalized["external"].append(normalize_service(ext, "external"))
|
||||||
|
|
||||||
|
for cloud in pick_list(spec, "clouds"):
|
||||||
|
raw_cloud = dict(cloud or {})
|
||||||
|
label = pick(raw_cloud, "label", "name", "title")
|
||||||
|
cloud_id = pick(raw_cloud, "id", "name") or id_gen.make("cloud", label)
|
||||||
|
normalized_cloud = dict(raw_cloud)
|
||||||
|
normalized_cloud["id"] = str(cloud_id)
|
||||||
|
normalized_cloud["label"] = label or str(cloud_id)
|
||||||
|
normalized_cloud["services"] = [
|
||||||
|
normalize_service(svc, "cloud-svc", default_type="compute")
|
||||||
|
for svc in pick_list(raw_cloud, "services")
|
||||||
|
]
|
||||||
|
remember(raw_cloud, normalized_cloud["id"])
|
||||||
|
normalized["clouds"].append(normalized_cloud)
|
||||||
|
|
||||||
|
tenancy_spec = dict(spec.get("tenancy", {}) or {})
|
||||||
|
normalized_tenancy = dict(tenancy_spec)
|
||||||
|
normalized_tenancy["label"] = pick(tenancy_spec, "label", "name", "title", default="Oracle Cloud Infrastructure")
|
||||||
|
normalized_tenancy["regions"] = []
|
||||||
|
|
||||||
|
for region in pick_list(tenancy_spec, "regions"):
|
||||||
|
raw_region = dict(region or {})
|
||||||
|
region_label = pick(raw_region, "label", "name", "title")
|
||||||
|
region_id = pick(raw_region, "id", "name") or id_gen.make("region", region_label)
|
||||||
|
normalized_region = dict(raw_region)
|
||||||
|
normalized_region["id"] = str(region_id)
|
||||||
|
normalized_region["label"] = region_label or str(region_id)
|
||||||
|
normalized_region["availability_domains"] = []
|
||||||
|
normalized_region["vcns"] = []
|
||||||
|
normalized_region["compartments"] = []
|
||||||
|
normalized_region["observability"] = pick_list(raw_region, "observability")
|
||||||
|
remember(raw_region, normalized_region["id"])
|
||||||
|
|
||||||
|
for ad in pick_list(raw_region, "availability_domains"):
|
||||||
|
raw_ad = dict(ad or {})
|
||||||
|
ad_label = pick(raw_ad, "label", "name", "title")
|
||||||
|
ad_id = pick(raw_ad, "id", "name") or id_gen.make("ad", ad_label)
|
||||||
|
normalized_ad = dict(raw_ad)
|
||||||
|
normalized_ad["id"] = str(ad_id)
|
||||||
|
normalized_ad["label"] = ad_label or str(ad_id)
|
||||||
|
remember(raw_ad, normalized_ad["id"])
|
||||||
|
normalized_region["availability_domains"].append(normalized_ad)
|
||||||
|
|
||||||
|
def normalize_vcn(raw_vcn: dict) -> dict:
|
||||||
|
vcn_label = pick(raw_vcn, "label", "name", "title")
|
||||||
|
vcn_id = pick(raw_vcn, "id", "name") or id_gen.make("vcn", vcn_label)
|
||||||
|
normalized_vcn = dict(raw_vcn)
|
||||||
|
normalized_vcn["id"] = str(vcn_id)
|
||||||
|
normalized_vcn["label"] = vcn_label or str(vcn_id)
|
||||||
|
parent_ref = pick(raw_vcn, "parent", default="")
|
||||||
|
if parent_ref:
|
||||||
|
normalized_vcn["parent"] = resolve_ref(parent_ref) or str(parent_ref)
|
||||||
|
normalized_vcn["gateways"] = [
|
||||||
|
normalize_service(gw, "gw", default_type="drg" if pick(gw, "type", "service_type") in ("drg", "dynamic_routing_gateway") else "compute")
|
||||||
|
for gw in pick_list(raw_vcn, "gateways")
|
||||||
|
]
|
||||||
|
normalized_vcn["subnets"] = []
|
||||||
|
remember(raw_vcn, normalized_vcn["id"])
|
||||||
|
|
||||||
|
for subnet in pick_list(raw_vcn, "subnets"):
|
||||||
|
raw_subnet = dict(subnet or {})
|
||||||
|
subnet_label = pick(raw_subnet, "label", "name", "title")
|
||||||
|
subnet_id = pick(raw_subnet, "id", "name") or id_gen.make("subnet", subnet_label)
|
||||||
|
normalized_subnet = dict(raw_subnet)
|
||||||
|
normalized_subnet["id"] = str(subnet_id)
|
||||||
|
normalized_subnet["label"] = subnet_label or str(subnet_id)
|
||||||
|
normalized_subnet["services"] = [
|
||||||
|
normalize_service(svc, "svc", default_type="compute")
|
||||||
|
for svc in pick_list(raw_subnet, "services")
|
||||||
|
]
|
||||||
|
remember(raw_subnet, normalized_subnet["id"])
|
||||||
|
normalized_vcn["subnets"].append(normalized_subnet)
|
||||||
|
return normalized_vcn
|
||||||
|
|
||||||
|
for vcn in pick_list(raw_region, "vcns"):
|
||||||
|
normalized_region["vcns"].append(normalize_vcn(dict(vcn or {})))
|
||||||
|
|
||||||
|
for comp in pick_list(raw_region, "compartments"):
|
||||||
|
raw_comp = dict(comp or {})
|
||||||
|
comp_label = pick(raw_comp, "label", "name", "title")
|
||||||
|
comp_id = pick(raw_comp, "id", "name") or id_gen.make("compartment", comp_label)
|
||||||
|
normalized_comp = dict(raw_comp)
|
||||||
|
normalized_comp["id"] = str(comp_id)
|
||||||
|
normalized_comp["label"] = comp_label or str(comp_id)
|
||||||
|
normalized_comp["vcns"] = [
|
||||||
|
normalize_vcn(dict(vcn or {}))
|
||||||
|
for vcn in pick_list(raw_comp, "vcns")
|
||||||
|
]
|
||||||
|
remember(raw_comp, normalized_comp["id"])
|
||||||
|
normalized_region["compartments"].append(normalized_comp)
|
||||||
|
|
||||||
|
normalized_tenancy["regions"].append(normalized_region)
|
||||||
|
|
||||||
|
normalized["tenancy"] = normalized_tenancy
|
||||||
|
|
||||||
|
onprem_spec = spec.get("onprem")
|
||||||
|
if not onprem_spec:
|
||||||
|
onprem_spec = spec.get("on_prem")
|
||||||
|
if onprem_spec:
|
||||||
|
raw_onprem = dict(onprem_spec or {})
|
||||||
|
onprem_label = pick(raw_onprem, "label", "name", "title", default="On-Premises Data Center")
|
||||||
|
normalized_onprem = dict(raw_onprem)
|
||||||
|
normalized_onprem["id"] = pick(raw_onprem, "id", "name", default="onprem") or "onprem"
|
||||||
|
normalized_onprem["label"] = onprem_label
|
||||||
|
normalized_onprem["services"] = [
|
||||||
|
normalize_service(svc, "onprem-svc", default_type="compute")
|
||||||
|
for svc in pick_list(raw_onprem, "services")
|
||||||
|
]
|
||||||
|
remember(raw_onprem, normalized_onprem["id"])
|
||||||
|
normalized["onprem"] = normalized_onprem
|
||||||
|
|
||||||
|
for conn in pick_list(spec, "connections"):
|
||||||
|
raw_conn = dict(conn or {})
|
||||||
|
source = resolve_ref(pick(raw_conn, "from", "source", default=None))
|
||||||
|
target = resolve_ref(pick(raw_conn, "to", "target", default=None))
|
||||||
|
if not source or not target:
|
||||||
|
continue
|
||||||
|
normalized_conn = dict(raw_conn)
|
||||||
|
normalized_conn["from"] = source
|
||||||
|
normalized_conn["to"] = target
|
||||||
|
normalized_conn["type"] = pick(raw_conn, "type", "service_type", default="standard")
|
||||||
|
normalized["connections"].append(normalized_conn)
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
|
||||||
# Unicode circled numbers for flow badges ① ② ③ ... ⑳
|
# Unicode circled numbers for flow badges ① ② ③ ... ⑳
|
||||||
_CIRCLED_NUMS = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
_CIRCLED_NUMS = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||||
|
|
||||||
@@ -1314,6 +1528,7 @@ class OCIDiagramGenerator:
|
|||||||
Plus optional on-premises, external actors, and connections.
|
Plus optional on-premises, external actors, and connections.
|
||||||
See examples/diagram-spec.yaml for the full format.
|
See examples/diagram-spec.yaml for the full format.
|
||||||
"""
|
"""
|
||||||
|
spec = _normalize_diagram_spec(spec)
|
||||||
gen = cls()
|
gen = cls()
|
||||||
|
|
||||||
# External actors (users, internet, third-party)
|
# External actors (users, internet, third-party)
|
||||||
|
|||||||
Reference in New Issue
Block a user