Skip to content

Fix storage key generation for SUBSCRIPTION_ONLY scope #10

Description

@nielsweistra

Ticket ID: CP-SDK-010

Fix storage key generation for SUBSCRIPTION_ONLY scope

Background

The storage key generator in the SDK uses an inconsistent format for SUBSCRIPTION_ONLY scope. This causes key conflicts between resources from different subscriptions.

Current behavior (wrong)

# SUBSCRIPTION scope currently generates:
"subscription/providers/Microsoft.Authorization/policyAssignments/my-policy"

# SUBSCRIPTION_ONLY should generate:
"sub:a1b2c3d4.../policyAssignments/my-policy"

Desired behavior

Scope Storage key format
TENANT tenant/{ns}/{type}/{name}
MANAGEMENT_GROUP mg:{mg_id}/{type}/{name}
SUBSCRIPTION sub:{sub_id}/{rg}/{type}/{name}
SUBSCRIPTION_ONLY sub:{sub_id}/{type}/{name}
RESOURCE_GROUP rg:{sub_id}/{rg}/{type}/{name}
PARENT_RESOURCE parent:{parent_id}/{type}/{name}

Fix

def build_storage_key(
    scope: UniquenessScope,
    subscription_id: str = None,
    management_group_id: str = None,
    resource_group: str = None,
    resource_type: str = None,
    resource_name: str = None,
    parent_resource_id: str = None,
) -> str:
    """
    Generate a unique storage key for a resource.

    Use as a cache key or database primary key.
    """
    match scope:
        case UniquenessScope.TENANT:
            return f"tenant/{resource_type}/{resource_name}"

        case UniquenessScope.MANAGEMENT_GROUP:
            return f"mg:{management_group_id}/{resource_type}/{resource_name}"

        case UniquenessScope.SUBSCRIPTION:
            rg = resource_group or "default"
            return f"sub:{subscription_id}/{rg}/{resource_type}/{resource_name}"

        case UniquenessScope.SUBSCRIPTION_ONLY:
            # FIX: no resource group in key
            return f"sub:{subscription_id}/{resource_type}/{resource_name}"

        case UniquenessScope.RESOURCE_GROUP:
            if not resource_group:
                raise ValueError("resource_group required for RESOURCE_GROUP scope")
            return f"rg:{subscription_id}/{resource_group}/{resource_type}/{resource_name}"

        case UniquenessScope.PARENT_RESOURCE:
            import hashlib
            parent_hash = hashlib.sha256(parent_resource_id.encode()).hexdigest()[:12]
            return f"parent:{parent_hash}/{resource_type}/{resource_name}"

        case _:
            raise ValueError(f"Unsupported scope: {scope}")

Tests

class TestBuildStorageKey:
    def test_subscription_only_no_resource_group(self):
        key = build_storage_key(
            scope=UniquenessScope.SUBSCRIPTION_ONLY,
            subscription_id="a1b2c3d4",
            resource_type="policyAssignments",
            resource_name="itl-foundation",
        )
        assert key == "sub:a1b2c3d4/policyAssignments/itl-foundation"
        assert "resourceGroups" not in key
        assert "None" not in key

    def test_subscription_vs_subscription_only_different_keys(self):
        sub_key = build_storage_key(
            scope=UniquenessScope.SUBSCRIPTION,
            subscription_id="a1b2",
            resource_group="rg-test",
            resource_type="roleAssignments",
            resource_name="owner",
        )
        sub_only_key = build_storage_key(
            scope=UniquenessScope.SUBSCRIPTION_ONLY,
            subscription_id="a1b2",
            resource_type="roleAssignments",
            resource_name="owner",
        )
        assert sub_key != sub_only_key

Acceptance Criteria

  • SUBSCRIPTION_ONLY key contains no resource group segment
  • SUBSCRIPTION_ONLY key contains no None string
  • SUBSCRIPTION and SUBSCRIPTION_ONLY generate different keys for the same resource
  • All scope variants generate unique keys (no conflicts)
  • Tests pass for all scope combinations

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions