Roles
This commit is contained in:
@@ -20,6 +20,75 @@ logger = logging.getLogger(__name__)
|
|||||||
# Known Azure enum values not always captured in the catalog schema
|
# Known Azure enum values not always captured in the catalog schema
|
||||||
_KNOWN_ENUMS: dict[str, list[str]] = {
|
_KNOWN_ENUMS: dict[str, list[str]] = {
|
||||||
"principalType": ["User", "Group", "ServicePrincipal", "Device", "ForeignGroup"],
|
"principalType": ["User", "Group", "ServicePrincipal", "Device", "ForeignGroup"],
|
||||||
|
"roles": [
|
||||||
|
# Key Vault roles
|
||||||
|
"KEY_VAULT_ADMINISTRATOR",
|
||||||
|
"KEY_VAULT_CERTIFICATES_OFFICER",
|
||||||
|
"KEY_VAULT_CRYPTO_OFFICER",
|
||||||
|
"KEY_VAULT_CRYPTO_SERVICE_ENCRYPTION_USER",
|
||||||
|
"KEY_VAULT_CRYPTO_USER",
|
||||||
|
"KEY_VAULT_READER",
|
||||||
|
"KEY_VAULT_SECRETS_OFFICER",
|
||||||
|
"KEY_VAULT_SECRETS_USER",
|
||||||
|
# Storage roles
|
||||||
|
"STORAGE_BLOB_DATA_CONTRIBUTOR",
|
||||||
|
"STORAGE_BLOB_DATA_OWNER",
|
||||||
|
"STORAGE_BLOB_DATA_READER",
|
||||||
|
"STORAGE_QUEUE_DATA_CONTRIBUTOR",
|
||||||
|
"STORAGE_QUEUE_DATA_READER",
|
||||||
|
"STORAGE_TABLE_DATA_CONTRIBUTOR",
|
||||||
|
"STORAGE_TABLE_DATA_READER",
|
||||||
|
# Common Azure roles
|
||||||
|
"CONTRIBUTOR",
|
||||||
|
"OWNER",
|
||||||
|
"READER",
|
||||||
|
"USER_ACCESS_ADMINISTRATOR",
|
||||||
|
# App/Function roles
|
||||||
|
"WEBSITE_CONTRIBUTOR",
|
||||||
|
# Monitoring roles
|
||||||
|
"MONITORING_CONTRIBUTOR",
|
||||||
|
"MONITORING_METRICS_PUBLISHER",
|
||||||
|
"MONITORING_READER",
|
||||||
|
"LOG_ANALYTICS_CONTRIBUTOR",
|
||||||
|
"LOG_ANALYTICS_READER",
|
||||||
|
# SQL roles
|
||||||
|
"SQL_DB_CONTRIBUTOR",
|
||||||
|
"SQL_MANAGED_INSTANCE_CONTRIBUTOR",
|
||||||
|
"SQL_SECURITY_MANAGER",
|
||||||
|
"SQL_SERVER_CONTRIBUTOR",
|
||||||
|
],
|
||||||
|
"roleDefinitionIds": [
|
||||||
|
# Same list for roleDefinitionIds parameter
|
||||||
|
"KEY_VAULT_ADMINISTRATOR",
|
||||||
|
"KEY_VAULT_CERTIFICATES_OFFICER",
|
||||||
|
"KEY_VAULT_CRYPTO_OFFICER",
|
||||||
|
"KEY_VAULT_CRYPTO_SERVICE_ENCRYPTION_USER",
|
||||||
|
"KEY_VAULT_CRYPTO_USER",
|
||||||
|
"KEY_VAULT_READER",
|
||||||
|
"KEY_VAULT_SECRETS_OFFICER",
|
||||||
|
"KEY_VAULT_SECRETS_USER",
|
||||||
|
"STORAGE_BLOB_DATA_CONTRIBUTOR",
|
||||||
|
"STORAGE_BLOB_DATA_OWNER",
|
||||||
|
"STORAGE_BLOB_DATA_READER",
|
||||||
|
"STORAGE_QUEUE_DATA_CONTRIBUTOR",
|
||||||
|
"STORAGE_QUEUE_DATA_READER",
|
||||||
|
"STORAGE_TABLE_DATA_CONTRIBUTOR",
|
||||||
|
"STORAGE_TABLE_DATA_READER",
|
||||||
|
"CONTRIBUTOR",
|
||||||
|
"OWNER",
|
||||||
|
"READER",
|
||||||
|
"USER_ACCESS_ADMINISTRATOR",
|
||||||
|
"WEBSITE_CONTRIBUTOR",
|
||||||
|
"MONITORING_CONTRIBUTOR",
|
||||||
|
"MONITORING_METRICS_PUBLISHER",
|
||||||
|
"MONITORING_READER",
|
||||||
|
"LOG_ANALYTICS_CONTRIBUTOR",
|
||||||
|
"LOG_ANALYTICS_READER",
|
||||||
|
"SQL_DB_CONTRIBUTOR",
|
||||||
|
"SQL_MANAGED_INSTANCE_CONTRIBUTOR",
|
||||||
|
"SQL_SECURITY_MANAGER",
|
||||||
|
"SQL_SERVER_CONTRIBUTOR",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Catalog is baked into the image root at /bicep_modules_catalog.json
|
# Catalog is baked into the image root at /bicep_modules_catalog.json
|
||||||
@@ -268,6 +337,56 @@ class BicepModuleCatalog:
|
|||||||
iac_params = cls._iac_param_map(module_name)
|
iac_params = cls._iac_param_map(module_name)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
|
# Build snippet for full params block (shown first)
|
||||||
|
snippet_params = []
|
||||||
|
tabstop = 1
|
||||||
|
for param_name, param_info in ver_params.items():
|
||||||
|
iac = iac_params.get(param_name, {})
|
||||||
|
required = iac.get("required", False)
|
||||||
|
ptype = param_info.get("type", "any")
|
||||||
|
allowed = param_info.get("allowed", [])
|
||||||
|
|
||||||
|
# Include required params + first few optional params in snippet
|
||||||
|
if required or len(snippet_params) < 5:
|
||||||
|
if allowed:
|
||||||
|
# Enum: use placeholder with first allowed value
|
||||||
|
placeholder = f"'{allowed[0]}'"
|
||||||
|
elif ptype == "bool":
|
||||||
|
placeholder = "true"
|
||||||
|
elif ptype == "int":
|
||||||
|
placeholder = "0"
|
||||||
|
elif ptype == "array":
|
||||||
|
placeholder = "[]"
|
||||||
|
elif ptype == "object":
|
||||||
|
placeholder = "{{}}"
|
||||||
|
else:
|
||||||
|
placeholder = "''"
|
||||||
|
snippet_params.append(f" {param_name}: ${{{tabstop}:{placeholder}}}")
|
||||||
|
tabstop += 1
|
||||||
|
|
||||||
|
if snippet_params:
|
||||||
|
snippet_text = "\n" + "\n".join(snippet_params) + "\n"
|
||||||
|
required_count = sum(1 for p, i in ver_params.items()
|
||||||
|
if iac_params.get(p, {}).get("required", False))
|
||||||
|
items.append({
|
||||||
|
"label": "⚡ Fill params block",
|
||||||
|
"kind": 15, # Snippet
|
||||||
|
"detail": f"{len(snippet_params)} params ({required_count} required)",
|
||||||
|
"insertText": snippet_text,
|
||||||
|
"insertTextFormat": 2, # Snippet
|
||||||
|
"sortText": "0_lru_snippet_000",
|
||||||
|
"documentation": {
|
||||||
|
"kind": "markdown",
|
||||||
|
"value": (
|
||||||
|
f"**Fill params block**\n\n"
|
||||||
|
f"Inserts {len(snippet_params)} params for `{module_name}`.\n"
|
||||||
|
f"Use Tab to navigate between fields."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Individual param completions
|
||||||
for param_name, param_info in ver_params.items():
|
for param_name, param_info in ver_params.items():
|
||||||
ptype = param_info.get("type", "any")
|
ptype = param_info.get("type", "any")
|
||||||
allowed = param_info.get("allowed", [])
|
allowed = param_info.get("allowed", [])
|
||||||
|
|||||||
@@ -142,6 +142,18 @@ class _ProxySession:
|
|||||||
"has_open_quote": bool(value_m.group(2)),
|
"has_open_quote": bool(value_m.group(2)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if cursor is inside an array value for a param
|
||||||
|
# e.g. "roles: ['KEY_VAULT_" or "roles: [ '"
|
||||||
|
array_m = re.search(r"^\s*(\w+):\s*\[[^\]]*?('?)([^',\]]*)$", current)
|
||||||
|
if array_m and array_m.group(1) not in {"params", "name", "module", "resource"}:
|
||||||
|
return {
|
||||||
|
"type": "param_value",
|
||||||
|
"module": mod_name,
|
||||||
|
"version": mod_ver,
|
||||||
|
"param": array_m.group(1),
|
||||||
|
"has_open_quote": bool(array_m.group(2)),
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": "param",
|
"type": "param",
|
||||||
"module": mod_name,
|
"module": mod_name,
|
||||||
|
|||||||
@@ -173,8 +173,12 @@ def test_param_completions_injected_on_param_context():
|
|||||||
assert "roleDefinitionIds" in labels
|
assert "roleDefinitionIds" in labels
|
||||||
assert "principalId" in labels
|
assert "principalId" in labels
|
||||||
assert "principalType" in labels
|
assert "principalType" in labels
|
||||||
assert items[0]["sortText"].startswith("0_lru_param_")
|
# First item is now the snippet completion
|
||||||
assert items[0]["kind"] == 5 # Field
|
assert items[0]["label"] == "⚡ Fill params block"
|
||||||
|
assert items[0]["kind"] == 15 # Snippet
|
||||||
|
# Second item should be a regular param
|
||||||
|
assert items[1]["sortText"].startswith("0_lru_param_")
|
||||||
|
assert items[1]["kind"] == 5 # Field
|
||||||
|
|
||||||
|
|
||||||
def test_param_completion_items_have_insert_text():
|
def test_param_completion_items_have_insert_text():
|
||||||
@@ -182,7 +186,12 @@ def test_param_completion_items_have_insert_text():
|
|||||||
"roleassignments", versions=["1.1.x"], schema=_ROLEASSIGNMENTS_SCHEMA
|
"roleassignments", versions=["1.1.x"], schema=_ROLEASSIGNMENTS_SCHEMA
|
||||||
)]
|
)]
|
||||||
items = BicepModuleCatalog.param_completion_items("roleassignments", "1.1.x")
|
items = BicepModuleCatalog.param_completion_items("roleassignments", "1.1.x")
|
||||||
for item in items:
|
# First item should be the snippet completion
|
||||||
|
assert items[0]["label"] == "⚡ Fill params block"
|
||||||
|
assert items[0]["insertTextFormat"] == 2
|
||||||
|
assert "principalId" in items[0]["insertText"]
|
||||||
|
# Individual param items should have ": " suffix
|
||||||
|
for item in items[1:]:
|
||||||
assert item["insertText"].endswith(": ")
|
assert item["insertText"].endswith(": ")
|
||||||
|
|
||||||
|
|
||||||
@@ -302,6 +311,24 @@ def test_detect_param_value_context_open_quote():
|
|||||||
assert ctx["has_open_quote"] is True
|
assert ctx["has_open_quote"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_param_value_context_in_array():
|
||||||
|
"""Cursor inside array value → param_value context."""
|
||||||
|
lines = [
|
||||||
|
"module myMod 'br/modules:roleassignments:1.1.x' = {",
|
||||||
|
" params: {",
|
||||||
|
" roles: ['KEY_VAULT_", # ← cursor inside array element
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
session = _make_session_with_doc(URI, lines)
|
||||||
|
# character = len(" roles: ['KEY_VAULT_") = 23
|
||||||
|
ctx = session._detect_context(URI, {"line": 2, "character": 23})
|
||||||
|
assert ctx["type"] == "param_value"
|
||||||
|
assert ctx["module"] == "roleassignments"
|
||||||
|
assert ctx["param"] == "roles"
|
||||||
|
assert ctx["has_open_quote"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_param_value_items_from_catalog_allowed():
|
def test_param_value_items_from_catalog_allowed():
|
||||||
"""environmentType completions come from catalog 'allowed' field."""
|
"""environmentType completions come from catalog 'allowed' field."""
|
||||||
BicepModuleCatalog._modules = [_make_module(
|
BicepModuleCatalog._modules = [_make_module(
|
||||||
@@ -353,6 +380,24 @@ def test_param_value_items_known_enum_fallback():
|
|||||||
assert "User" in labels
|
assert "User" in labels
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_value_items_roles_enum():
|
||||||
|
"""roles parameter uses _KNOWN_ENUMS for Azure role completions."""
|
||||||
|
BicepModuleCatalog._modules = [_make_module(
|
||||||
|
"roleassignments",
|
||||||
|
versions=["1.1.x"],
|
||||||
|
schema={"1.1.x": {"parameters": {
|
||||||
|
"roles": {"type": "array"}, # no 'allowed' in catalog
|
||||||
|
}}},
|
||||||
|
)]
|
||||||
|
items = BicepModuleCatalog.param_value_completion_items(
|
||||||
|
"roleassignments", "1.1.x", "roles"
|
||||||
|
)
|
||||||
|
labels = [i["label"] for i in items]
|
||||||
|
assert "KEY_VAULT_SECRETS_USER" in labels
|
||||||
|
assert "STORAGE_BLOB_DATA_READER" in labels
|
||||||
|
assert "CONTRIBUTOR" in labels
|
||||||
|
|
||||||
|
|
||||||
def test_param_value_items_empty_for_free_string():
|
def test_param_value_items_empty_for_free_string():
|
||||||
"""A plain string param with no allowed values returns no completions."""
|
"""A plain string param with no allowed values returns no completions."""
|
||||||
BicepModuleCatalog._modules = [_make_module(
|
BicepModuleCatalog._modules = [_make_module(
|
||||||
|
|||||||
Reference in New Issue
Block a user