Files
regorus/tests/azure_policy/normalization/cases/round_trip.yaml
Anand Krishnamoorthi d36f952133 feat(azure-policy): add alias normalization and denormalization (#635)
* feat: add Azure Policy alias normalization/denormalization

Add normalizer and denormalizer for ARM JSON resources, enabling Azure
Policy alias short names to become direct paths into a flat structure.

- Normalizer: flattens properties wrappers, lowercases keys, resolves
  per-alias versioned ARM paths, handles sub-resource array flattening,
  element-level field remaps, and array base renames
- Denormalizer: reverses all transformations with casing restoration
- AliasRegistry: loads production alias catalogs and data policy manifests
- Types: serde deserialization for ARM provider alias formats
- YAML test suite: 13 test files covering normalize, denormalize, round-trip,
  data-plane, edge cases, malformed input, sub-resources, and registry API
- Benchmark suite for normalization performance

* feat: add FFI and C# bindings for alias normalization

- FFI: alias_registry.rs with C-compatible API for loading catalogs,
  normalizing resources, and denormalizing back to ARM JSON
- C#: AliasRegistry wrapper class with NativeMethods P/Invoke bindings
  and integration tests
- Updated Cargo.lock files for new serde_json dependency
2026-03-30 18:44:36 -05:00

450 lines
13 KiB
YAML

aliases_json: |
[
{
"namespace": "Microsoft.Storage",
"resourceTypes": [
{
"resourceType": "storageAccounts",
"aliases": [
{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
"defaultPath": "properties.isHnsEnabled",
"paths": []
}
]
}
]
},
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "networkSecurityGroups",
"aliases": [
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
"defaultPath": "properties.securityRules[*].properties.protocol",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
"defaultPath": "properties.securityRules[*].properties.access",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
"defaultPath": "properties.securityRules[*].name",
"paths": []
}
]
}
]
}
]
cases:
- note: round-trip simple resource
input:
name: myStorage
type: "Microsoft.Storage/storageAccounts"
location: westus2
sku:
name: Standard_LRS
properties:
supportsHttpsTrafficOnly: true
isHnsEnabled: false
round_trip: true
expected_round_trip:
name: myStorage
type: "Microsoft.Storage/storageAccounts"
location: westus2
sku:
name: Standard_LRS
properties:
supportsHttpsTrafficOnly: true
isHnsEnabled: false
- note: round-trip sub-resource
input:
name: myNsg
type: "Microsoft.Network/networkSecurityGroups"
properties:
securityRules:
- name: rule1
properties:
protocol: Tcp
access: Allow
round_trip: true
expected_round_trip:
name: myNsg
type: "Microsoft.Network/networkSecurityGroups"
properties:
securityRules:
- name: rule1
properties:
protocol: Tcp
access: Allow
- note: round-trip versioned array alias (element field remap)
aliases_json: |
[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "firewallPolicies",
"aliases": [
{
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
"defaultPath": "properties.rules[*].properties.priority",
"paths": [
{
"path": "properties.rules[*].properties.prio",
"apiVersions": ["2021-01-01"]
}
]
},
{
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
"defaultPath": "properties.rules[*].name",
"paths": []
}
]
}
]
}
]
input:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
properties:
rules:
- name: rule1
properties:
prio: 100
api_version: "2021-01-01"
round_trip: true
expected_round_trip:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
properties:
rules:
- name: rule1
properties:
prio: 100
# Regression: default and versioned paths both produce exactly one element
# remap (same count), but the source field differs. A length-only comparison
# would silently reuse the default aggregate, producing incorrect results for
# the versioned API version.
- note: round-trip versioned remap with same count but different source field
aliases_json: |
[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "firewallPolicies",
"aliases": [
{
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
"defaultPath": "properties.rules[*].properties.oldA",
"paths": [
{
"path": "properties.rules[*].properties.oldB",
"apiVersions": ["2023-06-01"]
}
]
},
{
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
"defaultPath": "properties.rules[*].name",
"paths": []
}
]
}
]
}
]
input:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
properties:
rules:
- name: rule1
properties:
oldB: 100
api_version: "2023-06-01"
round_trip: true
expected_round_trip:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
properties:
rules:
- name: rule1
properties:
oldB: 100
- note: round-trip systemData and extendedLocation
input:
name: myStorage
type: "Microsoft.Storage/storageAccounts"
location: westus2
identity:
type: SystemAssigned
principalId: pid-123
userAssignedIdentities:
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
systemData:
createdBy: user@example.com
createdByType: User
createdAt: "2023-01-01T00:00:00Z"
extendedLocation:
name: edge-site-1
type: EdgeZone
properties:
supportsHttpsTrafficOnly: true
expected_normalized:
name: myStorage
type: "Microsoft.Storage/storageAccounts"
location: westus2
identity:
type: SystemAssigned
principalid: pid-123
userassignedidentities:
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
systemdata:
createdby: user@example.com
createdbytype: User
createdat: "2023-01-01T00:00:00Z"
extendedlocation:
name: edge-site-1
type: EdgeZone
supportshttpstrafficonly: true
round_trip: true
expected_round_trip:
name: myStorage
type: "Microsoft.Storage/storageAccounts"
location: westus2
identity:
type: SystemAssigned
principalId: pid-123
userAssignedIdentities:
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
systemData:
createdBy: user@example.com
createdByType: User
createdAt: "2023-01-01T00:00:00Z"
extendedLocation:
name: edge-site-1
type: EdgeZone
properties:
supportsHttpsTrafficOnly: true
# Regression: element remap must remove the stale ARM source field from the
# normalized output. Without cleanup, both the alias short name AND the
# original ARM leaf key survive; casing restoration during denormalization
# then produces a duplicate key (e.g. both "prio" and "priority").
- note: element remap removes stale ARM source field
aliases_json: |
[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "firewallPolicies",
"aliases": [
{
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
"defaultPath": "properties.rules[*].properties.prio",
"paths": []
},
{
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
"defaultPath": "properties.rules[*].name",
"paths": []
}
]
}
]
}
]
input:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
properties:
rules:
- name: rule1
properties:
prio: 42
# Normalize: "prio" (ARM leaf) → "priority" (alias short name).
# The stale "prio" key must be removed from the normalized element.
expected_normalized:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
rules:
- name: rule1
priority: 42
# Round-trip: normalize → denormalize should restore ARM structure.
round_trip: true
expected_round_trip:
name: myPolicy
type: "Microsoft.Network/firewallPolicies"
properties:
rules:
- name: rule1
properties:
prio: 42
# Regression: same stale-field-cleanup scenario but with a dotted ARM leaf
# path (e.g. "config.value" rather than a single segment "prio").
# The remove_element_field helper must navigate to the parent object and
# remove the leaf key via remove_at_dotted_path.
- note: element remap removes stale dotted ARM source field
aliases_json: |
[
{
"namespace": "Microsoft.Test",
"resourceTypes": [
{
"resourceType": "widgets",
"aliases": [
{
"name": "Microsoft.Test/widgets/items[*].rating",
"defaultPath": "properties.items[*].properties.config.score",
"paths": []
},
{
"name": "Microsoft.Test/widgets/items[*].name",
"defaultPath": "properties.items[*].name",
"paths": []
}
]
}
]
}
]
input:
type: "Microsoft.Test/widgets"
properties:
items:
- name: w1
properties:
config:
score: 5
# Normalize: "config.score" (dotted ARM leaf) → "rating" (alias short name).
# The stale "config.score" path must be removed; only "rating" should remain.
expected_normalized:
type: "Microsoft.Test/widgets"
items:
- name: w1
config: {}
rating: 5
# Round-trip: normalize → denormalize should restore ARM structure.
round_trip: true
expected_round_trip:
type: "Microsoft.Test/widgets"
properties:
items:
- name: w1
properties:
config:
score: 5
# Regression: array base rename must move (not clone) the value so that
# the stale ARM base key does not survive in the normalized output.
# Without the removal, denormalization produces a duplicate key.
- note: array base rename removes stale ARM base key
aliases_json: |
[
{
"namespace": "Microsoft.Test",
"resourceTypes": [
{
"resourceType": "widgets",
"aliases": [
{
"name": "Microsoft.Test/widgets/items[*].label",
"defaultPath": "properties.entries[*].label",
"paths": []
}
]
}
]
}
]
input:
type: "Microsoft.Test/widgets"
properties:
entries:
- label: hello
# Normalize: ARM base "entries" → alias base "items".
# The stale "entries" key must be removed; only "items" should remain.
expected_normalized:
type: "Microsoft.Test/widgets"
items:
- label: hello
# Round-trip: normalize → denormalize should restore ARM structure.
round_trip: true
expected_round_trip:
type: "Microsoft.Test/widgets"
properties:
entries:
- label: hello
# Regression: denormalize reverse element remap with a dotted ARM target
# must preserve restored casing (e.g. "Config.Score") rather than
# re-lowercasing it.
- note: round-trip dotted element remap preserves ARM casing
aliases_json: |
[
{
"namespace": "Microsoft.Test",
"resourceTypes": [
{
"resourceType": "widgets",
"aliases": [
{
"name": "Microsoft.Test/widgets/items[*].rating",
"defaultPath": "properties.items[*].properties.Config.Score",
"paths": []
},
{
"name": "Microsoft.Test/widgets/items[*].name",
"defaultPath": "properties.items[*].name",
"paths": []
}
]
}
]
}
]
input:
type: "Microsoft.Test/widgets"
properties:
items:
- name: w1
properties:
Config:
Score: 9
expected_normalized:
type: "Microsoft.Test/widgets"
items:
- name: w1
config: {}
rating: 9
round_trip: true
expected_round_trip:
type: "Microsoft.Test/widgets"
properties:
items:
- name: w1
properties:
Config:
Score: 9