Skip to content
6 changes: 6 additions & 0 deletions .changes/unreleased/added-20260129-133802.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: added
body: Refactored `set` command validation to use blocklist approach instead of allowlist, allowing any query parameter except explicitly blocked ones
time: 2026-01-29T13:38:02.881103993Z
custom:
Author: may-hartov
AuthorLink: https://github.com/may-hartov
20 changes: 5 additions & 15 deletions docs/commands/fs/set.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,14 @@ fab set ws1.Workspace -q displayName -i "New Name" -f
- Paths must map directly to JSON paths **without** filters or wildcards
- If the property path doesn't exist on the item definition, it will be added, provided it's valid according to the item's schema. The `set` command supports creation of 1 level at a time (e.g., to set `a.b.c`, first set `a.b`, then set `a.b.c`)
- Properties that expect a JSON string value are not supported - JSON input is always parsed as an object
- The following properties **cannot be set** for any Fabric resource: `id`, `type`, `workspaceId`, `folderId`

## Query Support

The following table shows supported queries per resource type:

| Resource | Supported Queries |
|----------------|-------------------|
| **Item** | `displayName`, `description`, `properties` (`.VariableLibrary` only), [`definition.<path>`](#definition-paths) |
| **Workspace** | `displayName`, `description`, `sparkSettings` |
| **Capacity** | `sku.name` |
| **Domain** | `displayName`, `description`, `contributorsScope` |
| **Connection** | `displayName`, `privacyLevel`, `credentialDetails` |
| **Gateway** | `displayName`, `allowCloudConnectionRefresh`, `allowCustomConnectors`, `capacityId`, `inactivityMinutesBeforeSleep`, `numberOfMemberGateways` |
| **Spark Pool** | `name`, `nodeSize`, `autoScale.enabled`, `autoScale.minNodeCount`, `autoScale.maxNodeCount` |
| **Folder** | `displayName` |
| **Shortcut** | `name`, `target` |

<a id="definition-paths"></a>
To discover the available properties of a Fabric resource, use the [`get` command](get.md) to retrieve the resource's current state. However, note that **not all properties returned by `get` are settable**.

To determine which properties can be updated via the `set` command, refer to the [Microsoft Fabric REST API documentation](aka.ms/fabric-apis) and locate the Update API operation for your specific resource type. Only properties documented in the Update API are supported for modification.

!!! note "Setting Item Definition Properties"
For **Items**, you can set any explicit path within the `definition` structure using dot notation for nested properties (e.g. `definition.parts[0].property`).
Paths must map directly to JSON paths **without** filters or wildcards. Refer to the [Microsoft Fabric item definitions](https://learn.microsoft.com/en-us/rest/api/fabric/articles/item-management/definitions) for the complete definition structure.
Expand Down
10 changes: 8 additions & 2 deletions src/fabric_cli/commands/fs/set/fab_fs_set_capacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
from fabric_cli.utils import fab_cmd_set_utils as utils_set
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_CAPACITIES = ["sku.name"]
INVALID_QUERIES = [
"location",
"properties.provisioningState",
"properties.state",
"systemData",
"name",
]


def exec(virtual_ws_item: VirtualWorkspaceItem, args: Namespace) -> None:
Expand All @@ -20,7 +26,7 @@ def exec(virtual_ws_item: VirtualWorkspaceItem, args: Namespace) -> None:

query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_CAPACITIES)
utils_set.validate_query_not_in_blocklist(query, INVALID_QUERIES)

utils_set.print_set_warning()
if args.force or utils_ui.prompt_confirm():
Expand Down
4 changes: 2 additions & 2 deletions src/fabric_cli/commands/fs/set/fab_fs_set_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
from fabric_cli.utils import fab_mem_store as utils_mem_store
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_CONNECTIONS = ["displayName", "privacyLevel", "credentialDetails"]
INVALID_QUERIES = ["connectionDetails", "connectivityType", "gatewayId"]
CONECTIVITY_TYPE_KEY = "connectivityType"


def exec(connection: VirtualWorkspaceItem, args: Namespace) -> None:
query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_CONNECTIONS)
utils_set.validate_query_not_in_blocklist(query, INVALID_QUERIES)

utils_set.print_set_warning()
if args.force or utils_ui.prompt_confirm():
Expand Down
4 changes: 2 additions & 2 deletions src/fabric_cli/commands/fs/set/fab_fs_set_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
from fabric_cli.utils import fab_mem_store as utils_mem_store
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_DOMAINS = ["contributorsScope", "description", "displayName"]
INVALID_QUERIES = ["parentDomainId"]


def exec(virtual_ws_item: VirtualWorkspaceItem, args: Namespace) -> None:
query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_DOMAINS)
utils_set.validate_query_not_in_blocklist(query, INVALID_QUERIES)

utils_set.print_set_warning()
if args.force or utils_ui.prompt_confirm():
Expand Down
4 changes: 1 addition & 3 deletions src/fabric_cli/commands/fs/set/fab_fs_set_folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
from fabric_cli.utils import fab_mem_store as utils_mem_store
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_FOLDERS = ["displayName"]


def exec(folder: Folder, args: Namespace) -> None:
query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_FOLDERS)
utils_set.validate_query_not_in_blocklist(query)

utils_set.print_set_warning()
if args.force or utils_ui.prompt_confirm():
Expand Down
53 changes: 29 additions & 24 deletions src/fabric_cli/commands/fs/set/fab_fs_set_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,14 @@
from fabric_cli.utils import fab_mem_store as utils_mem_store
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_GATEWAYS = [
"displayName",
"allowCloudConnectionRefresh",
"allowCustomConnectors",
"capacityId",
"inactivityMinutesBeforeSleep",
"numberOfMemberGateways",
]

INVALID_QUERIES = ["publicKey", "version", "virtualNetworkAzureResource"]
SUPPORTED_GATEWAY_TYPES = ["OnPremises", "VirtualNetwork"]


def exec(gateway: VirtualWorkspaceItem, args: Namespace) -> None:
query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_GATEWAYS)
utils_set.validate_query_not_in_blocklist(query, INVALID_QUERIES)

utils_set.print_set_warning()
if args.force or utils_ui.prompt_confirm():
Expand All @@ -40,20 +32,7 @@ def exec(gateway: VirtualWorkspaceItem, args: Namespace) -> None:

gatewat_type = vwsi_gateway_def.get("type", "")

if gatewat_type not in SUPPORTED_GATEWAY_TYPES:
raise FabricCLIError(
ErrorMessages.Common.gateway_type_not_supported(gatewat_type),
fab_constant.ERROR_NOT_SUPPORTED,
)
elif gatewat_type == "OnPremises" and query.startswith(
("numberOfMemberGateways", "capacityId", "inactivityMinutesBeforeSleep")
):
raise FabricCLIError(
ErrorMessages.Common.gateway_property_not_supported_for_type(
query, "OnPremises"
),
fab_constant.ERROR_NOT_SUPPORTED,
)
validate_query_by_gateway_type(gatewat_type, query)

updated_def = utils_set.update_fabric_element(
vwsi_gateway_def, query, args.input
Expand Down Expand Up @@ -83,3 +62,29 @@ def _prep_for_updated_def(data, gatewat_type: str) -> str:
updated_def, gateway, utils_mem_store.upsert_gateway_to_cache
)
utils_ui.print_output_format(args, message="Gateway updated")


def validate_query_by_gateway_type(gateway_type: str, query: str) -> None:
if gateway_type not in SUPPORTED_GATEWAY_TYPES:
raise FabricCLIError(
ErrorMessages.Common.gateway_type_not_supported(gateway_type),
fab_constant.ERROR_NOT_SUPPORTED,
)
elif gateway_type == "OnPremises" and query.startswith(
("numberOfMemberGateways", "capacityId", "inactivityMinutesBeforeSleep")
):
raise FabricCLIError(
ErrorMessages.Common.gateway_property_not_supported_for_type(
query, "OnPremises"
),
fab_constant.ERROR_NOT_SUPPORTED,
)
elif gateway_type == "VirtualNetwork" and query.startswith(
("allowCloudConnectionRefresh", "allowCustomConnectors")
):
raise FabricCLIError(
ErrorMessages.Common.gateway_property_not_supported_for_type(
query, "VirtualNetwork"
),
fab_constant.ERROR_NOT_SUPPORTED,
)
4 changes: 1 addition & 3 deletions src/fabric_cli/commands/fs/set/fab_fs_set_onelake.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
from fabric_cli.utils import fab_cmd_set_utils as utils_set
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_SHORTCUT = ["name", "target"]


def onelake_shortcut(onelake: OneLakeItem, args: Namespace) -> None:
query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_SHORTCUT)
utils_set.validate_query_not_in_blocklist(query)

# Get shortcut
args.output = None
Expand Down
11 changes: 2 additions & 9 deletions src/fabric_cli/commands/fs/set/fab_fs_set_sparkpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,11 @@
from fabric_cli.utils import fab_mem_store as utils_mem_store
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_SPARKPOOL = [
"name",
"nodeSize",
"autoScale.enabled",
"autoScale.minNodeCount",
"autoScale.maxNodeCount",
]


def exec(virtual_item: VirtualItem, args: Namespace) -> None:
query = args.query
utils_set.validate_expression(query, JMESPATH_UPDATE_SPARKPOOL)

utils_set.validate_query_not_in_blocklist(query)

utils_set.print_set_warning()
if args.force or utils_ui.prompt_confirm():
Expand Down
15 changes: 10 additions & 5 deletions src/fabric_cli/commands/fs/set/fab_fs_set_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@
from fabric_cli.utils import fab_mem_store as utils_mem_store
from fabric_cli.utils import fab_ui as utils_ui

JMESPATH_UPDATE_WORKSPACE = [
"description",
"displayName",
"sparkSettings",
INVALID_QUERIES = [
"capacityId",
"capacityRegion",
"apiEndpoint",
"domainId",
"oneLakeEndpoints",
"workspaceIdentity",
"managedPrivateEndpoints",
"roleAssignments",
]


def exec(workspace: Workspace, args: Namespace) -> None:
query = args.query

utils_set.validate_expression(query, JMESPATH_UPDATE_WORKSPACE)
utils_set.validate_query_not_in_blocklist(query, INVALID_QUERIES)

# Get workspace
args.deep_traversal = True
Expand Down
3 changes: 3 additions & 0 deletions src/fabric_cli/core/fab_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,6 @@
ITEM_QUERY_DESCRIPTION,
ITEM_QUERY_PROPERTIES,
]

# Invalid query parameters for set command across all fabric resources
SET_COMMAND_INVALID_QUERIES = ["id", "type", "workspaceId", "folderId"]
4 changes: 4 additions & 0 deletions src/fabric_cli/errors/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,7 @@ def gateway_property_not_supported_for_type(
property_name: str, gateway_type: str
) -> str:
return f"Setting '{property_name}' is not supported for Gateway type '{gateway_type}'"

@staticmethod
def query_not_supported_for_set(query: str) -> str:
return f"Query '{query}' is not supported for set command"
38 changes: 36 additions & 2 deletions src/fabric_cli/utils/fab_cmd_set_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import base64
import json
from typing import Optional

from fabric_cli.core import fab_constant, fab_logger
from fabric_cli.core.fab_exceptions import FabricCLIError
Expand All @@ -26,7 +27,7 @@ def validate_item_query(query_value: str, item=None) -> None:
allowed_keys = fab_constant.ITEM_SET_ALLOWED_METADATA_KEYS + [
fab_constant.ITEM_QUERY_DEFINITION
]
validate_expression(query_value, allowed_keys)
validate_query_in_allowlist(query_value, allowed_keys)

if not utils_jmespath.is_simple_path_expression(query_value):
raise FabricCLIError(
Expand All @@ -47,7 +48,7 @@ def validate_item_query(query_value: str, item=None) -> None:
)


def validate_expression(expression: str, allowed_keys: list[str]) -> None:
def validate_query_in_allowlist(expression: str, allowed_keys: list[str]) -> None:
if not any(
expression == key or expression.startswith(f"{key}.") for key in allowed_keys
):
Expand All @@ -58,6 +59,39 @@ def validate_expression(expression: str, allowed_keys: list[str]) -> None:
)


def validate_query_not_in_blocklist(
query: str, resource_specific_invalid_queries: Optional[list] = None
) -> None:
"""Validate that a query is not blocklisted.

Blocks queries from SET_COMMAND_INVALID_QUERIES or resource_specific_invalid_queries,
or JMESPath expressions containing filters/wildcards.

Args:
query: Query string to validate.
resource_specific_invalid_queries: Optional additional invalid queries.

Raises:
FabricCLIError: If query is blocklisted or contains filters/wildcards.
"""
if not utils_jmespath.is_simple_path_expression(query):
raise FabricCLIError(
CommonErrors.query_contains_filters_or_wildcards(query),
fab_constant.ERROR_INVALID_QUERY,
)

all_invalid_queries = fab_constant.SET_COMMAND_INVALID_QUERIES.copy()
if resource_specific_invalid_queries:
all_invalid_queries.extend(resource_specific_invalid_queries)

for invalid_key in all_invalid_queries:
if query == invalid_key or query.startswith(f"{invalid_key}."):
raise FabricCLIError(
CommonErrors.query_not_supported_for_set(query),
fab_constant.ERROR_INVALID_QUERY,
)


def ensure_notebook_dependency(decoded_item_def: dict, query: str) -> dict:
dependency_types = ["lakehouse", "warehouse", "environment"]

Expand Down
Loading