Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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](https://learn.microsoft.com/en-us/rest/api/fabric/articles/) 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
6 changes: 4 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,15 @@
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)
# Validate against invalid queries - allow users to set any query parameter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove unnecessary comments

# and let the API validate if it's supported
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 = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a thought - do we want to expose those lists to users via set --help?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. This list represents the minimal set of explicitly blocked queries; the API may block additional ones. From the user’s perspective, it’s transparent whether the failure occurs on the CLI side or the API side. This is merely an optimization for early failure and not the source of truth for the complete blocklist.

"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 @@ -338,3 +338,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"
40 changes: 38 additions & 2 deletions src/fabric_cli/utils/fab_cmd_set_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,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 +47,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 +58,42 @@ def validate_expression(expression: str, allowed_keys: list[str]) -> None:
)


def validate_query_not_in_blocklist(
query: str, resource_specific_invalid_queries: 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.
"""
# Validate it's a simple path expression (no filters, wildcards, etc.)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove comments

if not utils_jmespath.is_simple_path_expression(query):
raise FabricCLIError(
CommonErrors.query_contains_filters_or_wildcards(query),
fab_constant.ERROR_INVALID_QUERY,
)

# Combine common invalid queries with resource-specific ones
all_invalid_queries = fab_constant.SET_COMMAND_INVALID_QUERIES.copy()
if resource_specific_invalid_queries:
all_invalid_queries.extend(resource_specific_invalid_queries)

# Check if query matches or is a sub-path of any invalid key
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
Loading