Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 8 additions & 0 deletions .changes/unreleased/breaking-20260329-150739.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kind: breaking
body: Remove default credential fallback; explicit token credential is now required
time: 2026-03-29T15:07:39.9569937+03:00
custom:
Author: shirasassoon
AuthorLink: https://github.com/shirasassoon
Issue: "909"
IssueLink: https://github.com/microsoft/fabric-cicd/issues/909
11 changes: 5 additions & 6 deletions src/fabric_cicd/fabric_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(
environment: str = "N/A",
workspace_id: Optional[str] = None,
workspace_name: Optional[str] = None,
token_credential: TokenCredential = None,
token_credential: Optional[TokenCredential] = None,
**kwargs,
) -> None:
"""
Expand All @@ -47,7 +47,7 @@ def __init__(
repository_directory: Local directory path of the repository where items are to be deployed from.
item_type_in_scope: Item types that should be deployed for a given workspace. If omitted, defaults to all available item types.
environment: The environment to be used for parameterization.
token_credential: The token credential to use for API requests.
token_credential: The token credential to use for API requests (e.g., AzureCliCredential, ClientSecretCredential).
kwargs: Additional keyword arguments.

Examples:
Expand Down Expand Up @@ -103,11 +103,10 @@ def __init__(
if token_credential is None:
if _is_fabric_runtime():
token_credential = _generate_fabric_credential()
logger.debug("Running in Fabric runtime - using generated Fabric credential for authentication.")
else:
# if credential is not defined, use DefaultAzureCredential
from azure.identity import DefaultAzureCredential

token_credential = DefaultAzureCredential()
msg = "A TokenCredential is required to authenticate API requests. Please pass a 'token_credential' (e.g., AzureCliCredential, ClientSecretCredential)."
raise InputError(msg, logger)
else:
token_credential = validate_token_credential(token_credential)

Expand Down
4 changes: 4 additions & 0 deletions tests/test_fabric_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import pytest
import yaml
from fixtures.credentials import DummyTokenCredential

from fabric_cicd.fabric_workspace import FabricWorkspace, constants

Expand Down Expand Up @@ -120,6 +121,7 @@ def _create_workspace(workspace_id, repository_directory, item_type_in_scope=Non
workspace_id=workspace_id,
repository_directory=repository_directory,
item_type_in_scope=item_type_in_scope,
token_credential=DummyTokenCredential(),
**kwargs,
)
# Call refresh methods to populate workspace data
Expand Down Expand Up @@ -1031,6 +1033,7 @@ def test_base_api_url_kwarg_raises_error(temp_workspace_dir, valid_workspace_id)
workspace_id=valid_workspace_id,
repository_directory=str(temp_workspace_dir),
base_api_url="https://custom.api.url",
token_credential=DummyTokenCredential(),
)

# Verify the error message contains the expected text
Expand Down Expand Up @@ -1578,6 +1581,7 @@ def test_mix_of_default_and_non_default_logical_ids(temp_workspace_dir, patched_
assert workspace.repository_items["Notebook"]["Git Notebook"].logical_id == unique_logical_id
assert workspace.repository_items["DataPipeline"]["Exported Pipeline"].logical_id == constants.DEFAULT_GUID


def test_publish_variable_library_only_calls_replace_parameters(
temp_workspace_dir, patched_fabric_workspace, valid_workspace_id
):
Expand Down
24 changes: 24 additions & 0 deletions tests/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from unittest.mock import MagicMock, patch

import pytest
from fixtures.credentials import DummyTokenCredential

import fabric_cicd.publish as publish
from fabric_cicd import constants
Expand Down Expand Up @@ -124,6 +125,7 @@ def test_publish_only_existing_item_types(mock_endpoint, temp_workspace_dir):
workspace = FabricWorkspace(
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(workspace)
Expand All @@ -148,6 +150,7 @@ def test_default_none_item_type_in_scope_includes_all_types(mock_endpoint, temp_
workspace = FabricWorkspace(
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
token_credential=DummyTokenCredential(),
)

expected_types = list(constants.ACCEPTED_ITEM_TYPES)
Expand All @@ -161,6 +164,7 @@ def test_empty_item_type_in_scope_list(mock_endpoint, temp_workspace_dir):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=[],
token_credential=DummyTokenCredential(),
)
assert workspace.item_type_in_scope == []

Expand All @@ -180,6 +184,7 @@ def test_invalid_item_types_in_scope(mock_endpoint, temp_workspace_dir):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["InvalidItemType"],
token_credential=DummyTokenCredential(),
)


Expand All @@ -193,6 +198,7 @@ def test_multiple_invalid_item_types_in_scope(mock_endpoint, temp_workspace_dir)
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["FakeType", "AnotherInvalidType"],
token_credential=DummyTokenCredential(),
)


Expand All @@ -206,6 +212,7 @@ def test_mixed_valid_and_invalid_item_types_in_scope(mock_endpoint, temp_workspa
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook", "BadType", "Environment"],
token_credential=DummyTokenCredential(),
)


Expand Down Expand Up @@ -245,6 +252,7 @@ def test_unpublish_feature_flag_warnings(mock_endpoint, temp_workspace_dir, capl
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Lakehouse", "Warehouse", "SQLDatabase", "Eventhouse"],
token_credential=DummyTokenCredential(),
)

publish.unpublish_all_orphan_items(workspace)
Expand Down Expand Up @@ -288,6 +296,7 @@ def test_unpublish_with_feature_flags_enabled(mock_endpoint, temp_workspace_dir,
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Lakehouse"],
token_credential=DummyTokenCredential(),
)

publish.unpublish_all_orphan_items(workspace)
Expand Down Expand Up @@ -339,6 +348,7 @@ def track_unpublish(_self, item_name, item_type):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.unpublish_all_orphan_items(workspace)
Expand Down Expand Up @@ -387,6 +397,7 @@ def track_unpublish(_self, item_name, item_type):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.unpublish_all_orphan_items(workspace, item_name_exclude_regex=r"^Protected.*")
Expand Down Expand Up @@ -436,6 +447,7 @@ def track_unpublish(_self, item_name, item_type):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.unpublish_all_orphan_items(workspace, items_to_include=["TargetOrphan.Notebook"])
Expand Down Expand Up @@ -477,6 +489,7 @@ def track_unpublish(_self, item_name, item_type):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.unpublish_all_orphan_items(workspace)
Expand Down Expand Up @@ -520,6 +533,7 @@ def mock_publish_mirroreddatabase():
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Lakehouse", "MirroredDatabase"],
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(workspace)
Expand Down Expand Up @@ -559,6 +573,7 @@ def test_folder_exclusion_with_regex(mock_endpoint, temp_workspace_dir):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook", "SemanticModel"],
token_credential=DummyTokenCredential(),
)

exclude_regex = r".*legacy.*"
Expand Down Expand Up @@ -593,6 +608,7 @@ def test_folder_exclusion_with_anchored_regex(mock_endpoint, temp_workspace_dir)
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

exclude_regex = r"^/legacy$"
Expand All @@ -619,6 +635,7 @@ def test_item_name_exclusion_still_works(mock_endpoint, temp_workspace_dir):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

exclude_regex = r".*DoNotPublish.*"
Expand Down Expand Up @@ -656,6 +673,7 @@ def test_folder_inclusion_with_folder_path_to_include(mock_endpoint, temp_worksp
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook", "SemanticModel"],
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(
Expand Down Expand Up @@ -692,6 +710,7 @@ def test_folder_inclusion_and_exclusion_together(mock_endpoint, temp_workspace_d
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

with pytest.raises(
Expand Down Expand Up @@ -719,6 +738,7 @@ def test_empty_folder_path_to_include_raises_error(mock_endpoint, temp_workspace
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

with pytest.raises(InputError, match="folder_path_to_include must not be an empty list"):
Expand Down Expand Up @@ -748,6 +768,7 @@ def test_folder_exclusion_with_items_to_include(mock_endpoint, temp_workspace_di
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(
Expand Down Expand Up @@ -778,6 +799,7 @@ def test_folder_inclusion_with_item_exclusion(mock_endpoint, temp_workspace_dir)
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(
Expand Down Expand Up @@ -808,6 +830,7 @@ def test_folder_inclusion_with_items_to_include(mock_endpoint, temp_workspace_di
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(
Expand Down Expand Up @@ -840,6 +863,7 @@ def test_all_filters_combined(mock_endpoint, temp_workspace_dir):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_workspace_dir),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)

publish.publish_all_items(
Expand Down
2 changes: 2 additions & 0 deletions tests/test_response_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from unittest.mock import MagicMock, patch

import pytest
from fixtures.credentials import DummyTokenCredential

import fabric_cicd.constants as constants
import fabric_cicd.publish as publish
Expand Down Expand Up @@ -91,6 +92,7 @@ def test_workspace_with_notebook(mock_endpoint):
workspace_id="12345678-1234-5678-abcd-1234567890ab",
repository_directory=str(temp_path),
item_type_in_scope=["Notebook"],
token_credential=DummyTokenCredential(),
)
# Manually set up repository items since we're patching the refresh methods
workspace.repository_items = {
Expand Down
3 changes: 3 additions & 0 deletions tests/test_subfolders.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from unittest.mock import MagicMock, patch

import pytest
from fixtures.credentials import DummyTokenCredential

from fabric_cicd.fabric_workspace import FabricWorkspace

Expand Down Expand Up @@ -110,6 +111,7 @@ def _create_workspace(workspace_id, repository_directory, item_type_in_scope, **
workspace_id=workspace_id,
repository_directory=repository_directory,
item_type_in_scope=item_type_in_scope,
token_credential=DummyTokenCredential(),
**kwargs,
)

Expand Down Expand Up @@ -268,6 +270,7 @@ def mock_invoke_side_effect(method, url, **_kwargs):
workspace_id=valid_workspace_id,
repository_directory=str(repository_with_subfolders),
item_type_in_scope=["Notebook", "DataPipeline"],
token_credential=DummyTokenCredential(),
)

# Call methods in the intended order to populate folder structures
Expand Down
Loading