Links an Azure resource group to an Oracle Cloud Infrastructure compartment, so Oracle Database@Azure resources have somewhere to land on the OCI side (
azurerm_oracle_resource_anchor). Targetshashicorp/azurerm ~> 4.0.
- 🔗 Creates the Azure-side object that links a resource group to an Oracle Cloud Infrastructure compartment, and reports that compartment's OCID.
⚠️ This family is not a Microsoft resource provider. Everything here lives under the third-partyOracle.Databaseprovider, and what it creates exists in OCI as well as in Azure.- 🔑 RBAC is written against
Oracle.Database/*. A role scoped toMicrosoft.*grants nothing here. - 📋 Three inputs, total:
name,resource_group_name,tags. There is nolocation— the region is computed from the resource group. - 🕳️ The family's one invisible dependency. No sibling module consumes this anchor's
id, so Terraform cannot infer that the anchor must exist first. Usedepends_on. - 💥 Destroying the anchor severs the compartment link that sibling resources depend on, and no plan says so.
💡 Why it matters: This is the smallest module in the family and the right one to read first, because it is where the family's defining fact becomes visible. Almost everything that goes wrong with Oracle Database@Azure in Terraform is a provider-registration, tenant-onboarding, or RBAC-namespace problem rather than a configuration error — and all three surface here, at the cheapest possible resource to fail on.
If this module saves you time, please consider supporting its continued development:
- ⭐ Star the repository on GitHub.
- 🤝 Connect on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart LR
rg["terraform-azurerm-resource-group"]
vnet["terraform-azurerm-virtual-network: the subnet must be DELEGATED for Oracle Database@Azure"]
kv["terraform-azurerm-key-vault: holds the admin_password, read at plan time"]
nsg["terraform-azurerm-network-security-group: the only thing guarding the SCAN listener ports"]
anchor["terraform-azurerm-oracle-resource-anchor: links the resource group to an OCI COMPARTMENT. No location input, it is computed."]
exa["terraform-azurerm-oracle-exadata-infrastructure: the RACK. Every argument force-new except tags."]
vault["terraform-azurerm-oracle-exascale-database-storage-vault: sized ONCE, there is NO grow operation"]
cluster["terraform-azurerm-oracle-cloud-vm-cluster: the GI cluster. Use hostname_actual, not hostname, for connections."]
dbsrv["azurerm_oracle_db_servers DATA SOURCE: OCI OCIDs that do not exist until the rack does"]
adb["terraform-azurerm-oracle-autonomous-database: keystone plus for_each BACKUPS. Network posture is a ONE-WAY DOOR."]
cfb["terraform-azurerm-oracle-autonomous-database-clone-from-backup: a POINT-IN-TIME restore into a NEW database"]
cfd["terraform-azurerm-oracle-autonomous-database-clone-from-database: a clone of CURRENT state, optionally refreshable"]
note["PEERS, NOT CHILDREN: both clones import to the SAME Oracle.Database/autonomousDatabases path the keystone uses, so each IS an autonomous database"]
rg -->|"resource_group_name"| anchor
rg -->|"resource_group_name, location"| exa
rg -->|"resource_group_name, location"| vault
rg -->|"resource_group_name, location"| cluster
rg -->|"resource_group_name, location"| adb
anchor -->|"REQUIRED FIRST, but NO module consumes its id, so use depends_on"| exa
anchor -->|"same invisible dependency"| adb
exa -->|"id as cloud_exadata_infrastructure_id"| cluster
exa -->|"read AFTER it exists"| dbsrv
dbsrv -->|"OCIDs as db_servers"| cluster
vault -->|"NO Terraform link exists. Aligned by REGION and ZONE only."| cluster
vnet -->|"id plus a delegated subnet id"| cluster
vnet -->|"id plus subnet id, the PRIVATE posture"| adb
nsg -->|"restricts the unencrypted SCAN port 1521"| cluster
kv -->|"secret read as admin_password"| adb
kv -->|"secret read as admin_password"| cfb
kv -->|"secret read as admin_password"| cfd
adb -->|"id as source_autonomous_database_id"| cfb
adb -->|"id as source_autonomous_database_id"| cfd
note -->|"why they are separate modules"| cfb
note -->|"why they are separate modules"| cfd
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class exa keystone;
class anchor,vault,cluster,adb,cfb,cfd me;
class rg,vnet,kv,nsg,dbsrv,note sib;
flowchart TB
third["READ FIRST: this family is NOT a Microsoft resource provider. Everything lives under the third-party Oracle.Database provider."]
dual["so what these modules create exists in ORACLE CLOUD INFRASTRUCTURE as well as in Azure. Two clouds, one Terraform apply."]
reg["consequence one: Oracle.Database must be REGISTERED explicitly. It is not a Microsoft namespace and is not registered by default."]
rbac["consequence two: least-privilege roles are written against Oracle.Database/* . A role scoped to Microsoft.* grants NOTHING here."]
onboard["consequence three: the TENANT needs Oracle Database@Azure onboarding, the marketplace subscription and the Azure-to-Oracle account link. Without it nothing under Oracle.Database can be created at all."]
job["THE ANCHOR'S JOB: link an Azure resource group to an OCI COMPARTMENT, giving the family's resources somewhere to land on the OCI side"]
tiny["the input surface is THREE fields: name, resource_group_name, tags. That is all."]
noloc["there is NO location argument. The region is COMPUTED from the resource group, which is unusual in this library."]
compart["linked_compartment_id is COMPUTED and is the module's real product: the OCID someone with OCI console access needs, and the form Oracle support asks for"]
invisible["THE FAMILY'S ONE INVISIBLE DEPENDENCY: no sibling module consumes the anchor's id, so Terraform CANNOT infer that the anchor must exist first"]
fix["so a composition that needs the ordering states it with depends_on, or relies on the resource group they share"]
destroy["and destroying the anchor SEVERS the OCI compartment link that sibling resources depend on. Destroy those first. No plan says so."]
notags["tags apply on the AZURE side only. They are not propagated to the linked OCI compartment."]
this["terraform-azurerm-oracle-resource-anchor"]
keystone["azurerm_oracle_resource_anchor.this"]
third -->|"read this first"| dual
dual -->|"so"| reg
reg -->|"and"| rbac
rbac -->|"and"| onboard
onboard -->|"prerequisites"| this
job -->|"the job"| tiny
tiny -->|"note"| noloc
noloc -->|"computed"| compart
compart -->|"the real product"| this
invisible -->|"remedy"| fix
fix -->|"ordering"| destroy
destroy -->|"hazard"| this
notags -->|"boundary"| this
this -->|"creates"| keystone
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class keystone keystone;
class third,dual,reg,rbac,onboard,job,tiny,noloc,compart,invisible,fix,destroy,notags sib;
Resource inventory
| Resource | Count | Notes |
|---|---|---|
azurerm_oracle_resource_anchor.this |
1 | The keystone. name and resource_group_name force-new; location and linked_compartment_id computed. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Azure resource provider | Oracle.Database — a third-party namespace, API 2025-09-01 |
| Provider block | None in this module. The caller configures provider "azurerm", including the mandatory features {} block, and supplies authentication. |
Schema notes that bite — confirmed against the live provider schema and its documentation:
Oracle.Databaseis a third-party resource provider. It must be registered explicitly on the subscription, and RBAC for it is written againstOracle.Database/*.nameandresource_group_nameare force-new.- There is no
locationargument, and the region does not follow the resource group. The provider sendsLocation: "global"— hard-coded and unconditional — so thelocationoutput always readsglobalregardless of where the resource group lives. Unusual in this library, where regional resources take alocationvalue. linked_compartment_idis computed and is the module's real product.tagsare the only mutable field, and they are not propagated to the linked OCI compartment.- No sibling module consumes this anchor's
id, so the ordering it establishes is invisible to Terraform's dependency graph.
| Scope | Role / permission | Why |
|---|---|---|
| The resource group | Contributor, or a custom role with Oracle.Database/resourceAnchors/* |
Creating the anchor is a write against the Oracle.Database provider. |
⚠️ Note the namespace. Least-privilege custom roles for this family are written againstOracle.Database/*, notMicrosoft.*. A carefully-scoped role covering only Microsoft providers grants nothing here, and the resulting failure is an authorization error that looks like a bug rather than a missing grant.
- The
Oracle.Databaseresource provider registered on the subscription. This is the prerequisite most likely to be missing, because it is not a Microsoft namespace and is not registered by default. - Oracle Database@Azure onboarding completed for the tenant — the marketplace subscription and the link between the Azure tenant and an Oracle Cloud account. Without it, nothing under
Oracle.Databasecan be created, and the failure is a tenant-level one this module cannot report. - An existing resource group in a region where Oracle Database@Azure is offered. The supported set is smaller than Azure's overall footprint.
terraform-azurerm-oracle-resource-anchor/
├── providers.tf # required_version + the pinned azurerm provider. No provider block.
├── variables.tf # name, resource_group_name, tags, timeouts
├── main.tf # the keystone anchor
├── outputs.tf # id, name, resource_group_name, computed location, linked_compartment_id
├── README.md # this document
├── SCOPE.md # the cross-module contract
├── LICENSE # MIT
└── .gitignore
provider "azurerm" {
features {}
}
module "oracle_anchor" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-oracle-resource-anchor.git?ref=v1.0.0"
name = "anchor-oracle-prod"
resource_group_name = module.rg.name
tags = { workload = "oracle-database" }
}ℹ️ The caller configures the provider, its authentication, and the mandatory
features {}block. This module declares none of them.
💡 No
location— the provider hard-codes it toglobal. See example 3.
Consumes
| Input | Type | Source module |
|---|---|---|
resource_group_name |
string |
terraform-azurerm-resource-group → name |
name |
string |
caller decision |
tags |
map(string) |
caller decision |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
The anchor's Resource ID. | imports, diagnostics, RBAC |
name |
The anchor's name. Force-new. | review |
resource_group_name |
The group the anchor belongs to. Force-new. | review |
location |
The Azure region. Computed. | review |
linked_compartment_id |
The linked OCI compartment's OCID. Computed. | OCI-side investigation, support cases |
1 · ⚠️ Register the provider first — this is not a Microsoft namespace
# The prerequisite most likely to be missing.
az provider register --namespace Oracle.Database
az provider show --namespace Oracle.Database --query registrationState
⚠️ Oracle.Databaseis not registered by default in most subscriptions, because it is not a Microsoft namespace. Every resource in this family fails without it.
💡 Registering it is a subscription-level, one-time operation and deliberately not something this module does. Provider registration is a subscription concern, and a module that registered namespaces as a side effect would be reaching well outside its scope.
ℹ️ Registration is also not the whole story — see example 2.
2 · ⚠️ The tenant needs Oracle Database@Azure onboarding
Beyond provider registration, the TENANT needs:
- an Oracle Database@Azure marketplace subscription, and
- the link between the Azure tenant and an Oracle Cloud account.
⚠️ Without onboarding, nothing underOracle.Databasecan be created at all — including this anchor. The failure is a tenant-level one, so it cannot be fixed from a resource group and this module cannot report it usefully.
💡 If a first apply of this module fails and the provider is registered, onboarding is the next thing to check. That ordering saves time, because the two failures look similar from a Terraform error message.
ℹ️ This is why the anchor is worth applying on its own first: it is the cheapest resource in the family, so it is the cheapest place to discover a tenant-level problem. Discovering it on an Exadata rack apply is considerably more expensive.
3 · There is no `location` — it is computed
module "oracle_anchor" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-oracle-resource-anchor.git?ref=v1.0.0"
name = "anchor-oracle-prod"
resource_group_name = module.rg.name
# no location — the anchor takes its region from the resource group
}
output "where_it_landed" {
value = module.oracle_anchor.location
}ℹ️ Unusual in this library, where a regional resource normally takes a
locationvalue. The provider markslocationcomputed on this resource type, so the module has no such input — adding one for consistency would mean inventing an argument the provider does not accept.
💡 The practical consequence: to place the anchor in a particular region, place the resource group there.
4 · `linked_compartment_id` is the module's real product
output "oci_compartment" {
value = module.oracle_anchor.linked_compartment_id
}
# => "ocid1.compartment.oc1..aaaaaaaa..."💡 This is the bridge between the Azure view and the OCI view. It is the identifier someone with OCI console access needs in order to find the compartment these Azure resources actually live in, and the form Oracle support will ask for when you open a case.
ℹ️ Computed, so it appears after apply. Worth surfacing in a composition's outputs rather than leaving buried in state — the moment someone needs it is usually the moment they are debugging under time pressure and do not want to be reading state files.
⚠️ The OCID identifies a compartment in Oracle Cloud. Its contents, its policies and its OCI-side IAM are not Azure resources and are not manageable from Terraform's azurerm provider.
5 · ⚠️ The family's invisible dependency
module "oracle_anchor" {
source = "git::.../terraform-azurerm-oracle-resource-anchor.git?ref=v1.0.0"
name = "anchor-oracle-prod"
resource_group_name = module.rg.name
}
# FRAGMENT: shown to make the point about the missing dependency edge. The rack's own required
# inputs are in the end-to-end example below.
module "exadata" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-oracle-exadata-infrastructure.git?ref=v1.0.0"
zones = local.oracle_zone
resource_group_name = module.rg.name
# ⚠️ nothing here references module.oracle_anchor — Terraform sees NO dependency
depends_on = [module.oracle_anchor] # ...so state it explicitly
}
⚠️ No sibling module consumes the anchor'sid, because no sibling resource takes it as an argument. Terraform therefore cannot infer that the anchor must exist before the Exadata infrastructure, the VM cluster or the databases.
💡
depends_onis the fix. Without it, Terraform is free to create an Oracle resource before the compartment link exists, and the failure is an apply-time error that names neither module.
ℹ️ In practice both modules usually depend on the same resource group, which gives some ordering — but not enough, because the resource group can exist long before the anchor does.
6 · Destroying the anchor is an ordering hazard
# module.oracle_anchor.azurerm_oracle_resource_anchor.this will be destroyed
⚠️ This severs the OCI compartment link that sibling resources depend on. Destroy the Oracle databases, clusters and infrastructure first.
⚠️ Nothing in the plan says so, and that follows directly from example 5: because no sibling references the anchor, Terraform has no reason to order the destroys correctly either. A plan that removes the anchor while Oracle databases still exist looks perfectly ordinary.
💡 Removing this module is cheap to type and expensive to mean. Where a composition manages the whole family, the same
depends_onthat fixes creation order also fixes destroy order — Terraform reverses it.
7 · RBAC is written against `Oracle.Database/*`
# ✅ A custom role that actually works for this family.
module "oracle_operator_role" {
source = "git::.../terraform-azurerm-role-definition.git?ref=v1.0.0"
scope = "scope"
name = "Oracle Database at Azure Operator"
permissions = {
actions = [
"Oracle.Database/resourceAnchors/*",
"Oracle.Database/cloudExadataInfrastructures/*",
"Oracle.Database/cloudVmClusters/*",
"Oracle.Database/autonomousDatabases/*",
]
}
}# ❌ A carefully-scoped role that grants nothing here.
actions = ["Microsoft.Resources/*", "Microsoft.Network/*"]
⚠️ A role scoped to Microsoft providers grants nothing in this family. The resulting failure is an authorization error, which reads like a bug rather than a missing grant.
💡 Note the VM cluster module needs both namespaces —
Oracle.Database/*for the cluster andMicrosoft.Network/virtualNetworks/subnets/join/actionfor the subnet join. A role covering only one of the two fails partway through an apply.
8 · Tags stop at the Azure boundary
tags = {
workload = "oracle-database"
cost-centre = "platform"
}ℹ️ Tags apply on the Azure side only. They are not propagated to the linked OCI compartment or to anything inside it.
⚠️ So an Azure cost-attribution tag does not attribute OCI-side consumption, and an OCI-side tagging requirement is a separate exercise carried out with Oracle's own tooling. Worth knowing before someone builds a chargeback report on the assumption that these tags cover everything.
💡
tagsis the only mutable field on this resource, which makes it the only thing here that can be corrected without a replacement.
9 · One anchor per resource group
locals {
oracle_groups = {
prod = "rg-oracle-prod"
test = "rg-oracle-test"
}
}
module "oracle_anchors" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-oracle-resource-anchor.git?ref=v1.0.0"
for_each = local.oracle_groups
name = "anchor-oracle-${each.key}"
resource_group_name = each.value
tags = { environment = each.key }
}💡 Keyed by environment, so adding a third never re-indexes the first two — which matters because
nameandresource_group_nameare both force-new.
ℹ️ Each anchor links its own resource group to its own OCI compartment, which is the natural way to keep production and test data separated on the OCI side as well as the Azure side.
⚠️ Read backlinked_compartment_idper environment and record it. When someone later asks "which OCI compartment is test in?", the answer should not require a state file.
10 · A destroy-and-recreate is not cosmetic
# module.oracle_anchor.azurerm_oracle_resource_anchor.this must be replaced
-/+ resource "azurerm_oracle_resource_anchor" "this" {
~ name = "anchor-oracle-prod" -> "anchor-oracle-production" # forces replacement
}
⚠️ A rename replaces the anchor, which means severing and re-establishing the OCI compartment link that sibling resources are provisioned against. This is not a name change; it is example 6 with an extra step.
💡 Both
nameandresource_group_nameare force-new, andtagsis the only field that is not. Get the name right the first time.
ℹ️
linked_compartment_idis computed, so a replacement may well produce a different compartment OCID. Anything recorded against the old value becomes stale.
11 · Adopting an anchor that already exists
terraform import 'module.oracle_anchor.azurerm_oracle_resource_anchor.this' \
'/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-oracle-prod/providers/Oracle.Database/resourceAnchors/anchor-oracle-prod'💡 A common real situation: the anchor was created through the portal during onboarding, and Terraform is being introduced afterwards. Importing is the right move — recreating it would sever and re-establish the compartment link (example 6).
ℹ️ Note the
Oracle.Databasesegment in the Resource ID. Copying the ID shape from aMicrosoft.*resource and substituting names will not produce a valid one.
⚠️ After importing, run a plan and expect it to be empty. If it proposes a replacement, thenameorresource_group_namein your configuration does not match the imported resource — fix the configuration rather than accepting the plan, because that replacement is a compartment relink.
💡
linked_compartment_idis populated by the read, so an import is also the quickest way to discover the OCID of a compartment somebody else set up.
12 · 🏗️ End-to-end composition
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-oracle-prod"
location = "eastus" # the anchor's region follows from here
}
# 1 · The anchor. Apply this first, on its own, the very first time —
# it is the cheapest place to discover a provider-registration or onboarding problem.
module "oracle_anchor" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-oracle-resource-anchor.git?ref=v1.0.0"
name = "anchor-oracle-prod"
resource_group_name = module.rg.name
tags = { workload = "oracle-database" }
}
# 2 · The rack. Note the depends_on — nothing here references the anchor.
module "exadata" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-oracle-exadata-infrastructure.git?ref=v1.0.0"
name = "exa-oracle-prod"
resource_group_name = module.rg.name
location = module.rg.location
display_name = "Oracle Production Exadata"
shape = "Exadata.X9M"
zones = ["2"]
compute_count = 2
storage_count = 3
customer_contacts = ["oracle-platform@example.com"] # a team alias — force-new
depends_on = [module.oracle_anchor] # the ordering Terraform cannot infer
}
output "oracle_landing_zone" {
value = {
anchor_id = module.oracle_anchor.id
region = module.oracle_anchor.location # computed
oci_compartment = module.oracle_anchor.linked_compartment_id # record this
}
}🔒 What the composition gets right: the anchor is applied before anything depends on it, the ordering is stated rather than assumed, the OCI compartment OCID is surfaced as an output instead of left in state, and the Exadata notification address is a team alias — which matters because that field is force-new.
⚠️ What no plan will tell you: whetherOracle.Databaseis registered, whether the tenant is onboarded, and whether the identity running Terraform holdsOracle.Database/*rather than onlyMicrosoft.*. All three produce apply-time failures (examples 1, 2, 7).
💡 On a genuinely first deployment, apply step 1 alone before adding step 2. An Exadata apply takes hours; finding out at the end of it that a namespace was not registered is an expensive way to learn.
| Input | Type | Default | Notes |
|---|---|---|---|
name |
string |
— | Required. Force-new. |
resource_group_name |
string |
— | Required. Force-new. The anchor's region follows from it. |
tags |
map(string) |
{} |
The only mutable field. Not propagated to OCI. |
timeouts |
object(...) |
null |
create / read / update / delete. |
Full schemas
variable "resource_group_name" {
type = string
# ℹ️ The anchor's `location` follows from this group rather than being supplied — which is why this
# module has no `location` variable, unlike every other module in this family.
# ⚠️ Immutable: changing it forces a new anchor to be created.
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
# ⚠️ Deleting the anchor is not a local operation. It severs the link to an OCI compartment that
# sibling Oracle Database@Azure resources depend on. Destroy those first.
}| Output | Description | Kind |
|---|---|---|
id |
Passthrough | |
name |
The anchor's name | Passthrough |
resource_group_name |
The resource group the anchor belongs to | Passthrough |
tags |
Tags assigned to the anchor on the Azure side | Passthrough |
linked_compartment_id |
Passthrough | |
has_linked_compartment |
Passthrough | |
location |
Passthrough | |
created_with_global_location |
Constant | |
resource_provider_namespace |
Derived | |
requires_resource_provider_registration |
Constant | |
azure_api_provider_version |
Derived | |
arm_properties_object_is_empty |
Constant | |
ordering_requires_explicit_depends_on |
Constant | |
consumed_by_arm_type |
Derived | |
destroy_severs_oci_compartment_link |
Constant | |
replacement_triggering_arguments |
Derived | |
only_mutable_argument |
Constant | Derived |
tags_apply_to_azure_side_only |
Constant |
🔒 Nothing sensitive is accepted or emitted. The compartment OCID is an identifier, not a credential.
-
The third-party provider namespace leads the documentation deliberately. Almost everything that goes wrong with Oracle Database@Azure in Terraform is a provider-registration, tenant-onboarding or RBAC-namespace problem rather than a configuration error, and all three fail here first. A reader who learns that from the smallest module in the family saves themselves discovering it on a multi-hour Exadata apply.
-
The module has no
locationvariable because the resource has nolocationargument. The provider marks it computed, following the resource group. Adding an input for consistency with the rest of the library would mean inventing something the provider does not accept. -
linked_compartment_idis treated as the module's product, not as an incidental attribute. An anchor whose OCID nobody recorded is an anchor that will be hunted for in a state file during an incident. The output description says what it is for rather than what it is. -
The invisible dependency is documented rather than solved with a synthetic input. This module could accept an unused handle purely to create a graph edge, but a variable that exists only to influence ordering misrepresents what the resource does. Naming the gap and pointing at
depends_onis more honest and equally actionable. -
The destroy hazard follows from the same fact and is stated separately anyway. Because no sibling references the anchor, Terraform cannot order the destroys correctly either — so the warning belongs in its own place rather than as a footnote to the creation-ordering note.
-
Provider registration is deliberately out of scope. It is a subscription-level concern, and a module that registered namespaces as a side effect of creating a resource would be reaching well outside what its name promises.
-
Tags are documented as stopping at the Azure boundary, because the natural assumption is that a tag on an Azure resource covers the cost it generates. Here a substantial part of that cost is incurred on the OCI side, where these tags do not reach.
-
tagsis the only mutable field, which makes this module unusually unforgiving for its size: two of its three inputs force replacement, and a replacement is an OCI compartment relink.
| Concern | Secure default (empty call) | Opt-out (caller must type it) |
|---|---|---|
| Provider namespace | stated up front, in the badge row and the RBAC table | — |
| Least privilege | RBAC documented against Oracle.Database/*, per resource type |
— |
| Tenant prerequisites | onboarding and registration named as prerequisites, not assumed | — |
| Ordering | the invisible dependency documented, with depends_on named |
— |
| Destroy safety | the compartment-relink consequence stated plainly | — |
| Scope discipline | provider registration left to the subscription owner | — |
| Cross-cloud boundary | tags documented as Azure-only | — |
| Secrets | none accepted, none emitted | — |
- Before the first apply: register
Oracle.Databaseand confirm tenant onboarding. - Before the first apply: confirm the identity holds
Oracle.Database/*, not onlyMicrosoft.*. - Before adding siblings: add
depends_onpointing at this module. - Before planning a destroy: destroy the Oracle resources that depend on the compartment link first.
- After the first apply: record
linked_compartment_idsomewhere a human can find it.
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the source to a tag —
?ref=v1.0.0— never a branch. - Plan-only from here. A human applies from CI.
⚠️ On a first deployment, apply this module alone before anything else in the family. It is the cheapest place to discover a registration or onboarding failure.⚠️ Read any plan that replaces or destroys this anchor as a compartment-relink plan, not a rename.- ℹ️ Only
tagsupdates in place. - ℹ️ Record
linked_compartment_idafter apply — it is what Oracle support will ask for.
terraform validate and terraform fmt -check are the offline gate. They confirm:
nameandresource_group_nameare present and typed correctly;- the
timeoutsvalue is an object of Go duration strings - but note that a key the type does not declare is SILENTLY DISCARDED rather than refused, so a misspelling here produces no error anywhere and the provider default quietly applies; - the module declares no
providerblock.
What only plan and apply exercise:
- whether the resource group exists;
- whether the anchor name is already taken.
What no Terraform command checks at any stage, and which is where this module actually fails:
- whether the
Oracle.Databaseresource provider is registered on the subscription; - whether the tenant is onboarded to Oracle Database@Azure;
- whether the identity holds
Oracle.Database/*permissions rather than onlyMicrosoft.*; - whether the resource group's region offers Oracle Database@Azure at all.
Outputs:
id = "/subscriptions/00000000-.../resourceGroups/rg-oracle-prod/providers/Oracle.Database/resourceAnchors/anchor-oracle-prod"
linked_compartment_id = "ocid1.compartment.oc1..aaaaaaaaexampleexampleexample"
location = "eastus"
name = "anchor-oracle-prod"
resource_group_name = "rg-oracle-prod"
ℹ️ Note the
Oracle.Databasesegment in the Resource ID rather than aMicrosoft.*one — the clearest single indication that this family is not what most azurerm resources are.
💡
linked_compartment_idis the value worth copying somewhere durable.
| Symptom | Cause | Fix |
|---|---|---|
| Apply fails with a provider-not-registered error | Oracle.Database is not registered — it is not a Microsoft namespace. |
az provider register --namespace Oracle.Database (example 1). |
| Apply fails and the provider is registered | The tenant is not onboarded to Oracle Database@Azure. | Complete the marketplace subscription and Oracle account link (example 2). |
| Authorization error despite a carefully-scoped role | The role covers Microsoft.* but not Oracle.Database/*. |
Add the Oracle namespace actions (example 7). |
| Wanted the anchor in a specific region and there is no input | location is computed from the resource group. |
Place the resource group in that region (example 3). |
| A sibling Oracle resource was created before the anchor | No sibling references the anchor, so Terraform saw no dependency. | Add depends_on (example 5). |
| A destroy removed the anchor while databases still existed | Same cause — no dependency edge to order the destroys. | Destroy dependants first (example 6). |
| A rename produced a different OCI compartment OCID | name is force-new, and the OCID is computed. |
Expected; record the new value (example 10). |
| Azure cost tags do not account for OCI-side spend | Tags stop at the Azure boundary. | Use Oracle's own tooling for OCI attribution (example 8). |
| Oracle support asked for an identifier and the Resource ID was not it | They want the OCI OCID. | Use linked_compartment_id (example 4). |
| The region was accepted but Oracle resources fail there | Oracle Database@Azure is not offered in every Azure region. | Choose a supported region. |
azurerm_oracle_resource_anchor— provider documentation for this resource.- Oracle Database@Azure overview — what the service is and how the Azure and OCI sides relate.
- Onboard to Oracle Database@Azure — the tenant-level prerequisite.
- Azure resource providers and registration — why a third-party namespace needs registering.
- OCI compartments — what
linked_compartment_idpoints at. - Sibling modules in this family:
terraform-azurerm-oracle-exadata-infrastructure,terraform-azurerm-oracle-cloud-vm-cluster,terraform-azurerm-oracle-exascale-database-storage-vault,terraform-azurerm-oracle-autonomous-database. - This module's
SCOPE.md.
💙 "Infrastructure as Code should be standardized, consistent, and secure."

