Skip to content

Commit 6e10991

Browse files
ayeshurunAlon Yeshurun
andauthored
feat: opt in bulk publish in deploy command (#260)
Co-authored-by: Alon Yeshurun <alonyeshurun+microsoft@microsoft.com>
1 parent 5874763 commit 6e10991

11 files changed

Lines changed: 3081 additions & 41 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: added
2+
body: Adds the `--bulk_publish` flag to the `deploy` command to opt into experimental bulk publish
3+
time: 2026-07-01T12:14:00.000000+00:00
4+
custom:
5+
Author: ayeshurun
6+
AuthorLink: https://github.com/ayeshurun

docs/commands/fs/deploy.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ The deployment to the Fabric workspaces is executed via the Fabric REST APIs.
5050

5151
---
5252

53+
### Bulk Publish (Experimental)
54+
55+
By default, `deploy` publishes items **one at a time**. You can optionally enable
56+
**bulk publish**, which deploys all items in a **single bulk import API call**.
57+
58+
Enable it with the `--bulk_publish` flag:
59+
60+
```bash
61+
fab deploy --config config.yml --target_env dev --bulk_publish
62+
```
63+
64+
Notes:
65+
66+
- This feature is **experimental** in the underlying [`fabric-cicd`](https://microsoft.github.io/fabric-cicd/latest/how_to/optional_feature/) library and uses its bulk import (beta) API. It may change or fail; omit the `--bulk_publish` flag to use the standard per-item publish behavior.
67+
- It is **disabled by default**, so existing deployments keep the standard per-item publish behavior.
68+
- When enabled, the CLI turns on the required `enable_experimental_features` and `enable_bulk_publish` fabric-cicd feature flags for you.
69+
70+
---
71+
5372
### Configuration Behavior
5473

5574
- The configuration file controls what is published and unpublished.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ dependencies = [
2929
"psutil==7.0.0",
3030
"requests",
3131
"cryptography",
32-
"fabric-cicd>=0.3.1",
32+
"fabric-cicd>=1.2.0",
3333
]
3434

3535
[project.scripts]

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ argcomplete>=3.6.2
1111
psutil==7.0.0
1212
requests
1313
cryptography
14-
fabric-cicd>=0.3.1
14+
fabric-cicd>=1.2.0
1515

1616
# Testing and Building Requirements
1717
tox>=4.20.0

src/fabric_cli/commands/fs/deploy/fab_fs_deploy_config_file.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def deploy_with_config_file(args: Namespace) -> None:
2929
# feature flags to avoid printing identity info in logs
3030
append_feature_flag("disable_print_identity")
3131

32+
# opt-in experimental bulk publish (single bulk import API call)
33+
_apply_bulk_publish_feature_flags(args)
34+
3235
deploy_config_file = args.config
3336
deploy_parameters = get_dict_from_params(args.params, max_depth=1)
3437
for param in deploy_parameters:
@@ -54,3 +57,23 @@ def deploy_with_config_file(args: Namespace) -> None:
5457
raise FabricCLIError(
5558
f"Deployment failed: {str(e)}",
5659
fab_constant.ERROR_IN_DEPLOYMENT)
60+
61+
62+
def _apply_bulk_publish_feature_flags(args: Namespace) -> None:
63+
"""Enable fabric-cicd bulk publish when the --bulk_publish flag is set.
64+
65+
Bulk publish deploys all items in a single bulk import API call instead of
66+
one item at a time. It is experimental in fabric-cicd and requires both the
67+
'enable_experimental_features' and 'enable_bulk_publish' feature flags, so
68+
the CLI appends both. Disabled by default to preserve backward-compatible
69+
standard (per-item) publish behavior.
70+
"""
71+
if getattr(args, "bulk_publish", False):
72+
append_feature_flag("enable_experimental_features")
73+
append_feature_flag("enable_bulk_publish")
74+
fab_ui.print_warning(
75+
"Experimental bulk publish is enabled: items will be deployed in a "
76+
"single bulk import API call. This feature is experimental in "
77+
"fabric-cicd and may change or fail; omit the '--bulk_publish' flag "
78+
"to use standard per-item publish."
79+
)

src/fabric_cli/parsers/fab_fs_parser.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,9 +453,11 @@ def register_import_parser(subparsers: _SubParsersAction) -> None:
453453
def register_deploy_parser(subparsers: _SubParsersAction) -> None:
454454
deploy_examples = [
455455
"# deploy fabric items to a workspace using a configuration file and target environment",
456-
"$ deploy --config_file config.yml --target_env dev\n",
456+
"$ deploy --config config.yml --target_env dev\n",
457457
"# deploy with config file, environment, and optional parameters",
458-
"$ deploy --config_file config.yml --target_env prod -P '[{\"param1\":\"value1\"}]' -f",
458+
"$ deploy --config config.yml --target_env prod -P '[{\"param1\":\"value1\"}]' -f\n",
459+
"# deploy using experimental bulk publish (single bulk import API call)",
460+
"$ deploy --config config.yml --target_env dev --bulk_publish",
459461
]
460462

461463
deploy_parser = subparsers.add_parser(
@@ -492,6 +494,13 @@ def register_deploy_parser(subparsers: _SubParsersAction) -> None:
492494
"-f", "--force", required=False, action="store_true", help="Force. Optional"
493495
)
494496

497+
deploy_parser.add_argument(
498+
"--bulk_publish",
499+
required=False,
500+
action="store_true",
501+
help="Experimental. Deploy all items in a single bulk import API call instead of one at a time. Optional",
502+
)
503+
495504
deploy_parser.set_defaults(func=lazy_command(
496505
_fs_module_path, 'deploy_command'))
497506
deploy_parser.usage = f"{utils_error_parser.get_usage_prog(deploy_parser)}"

tests/test_commands/api_processors/workspace_api_processor.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88

99
class WorkspaceAPIProcessor(BaseAPIProcessor):
10-
WORKSPACE_URI = "https://api.fabric.microsoft.com/v1/workspaces"
10+
WORKSPACE_URI = [
11+
"https://api.fabric.microsoft.com/v1/workspaces",
12+
"https://api.powerbi.com/v1/workspaces",
13+
]
1114

1215
def __init__(self, generated_name_mapping):
1316
self.generated_name_mapping = generated_name_mapping
@@ -19,7 +22,7 @@ def try_process_response(self, request, response) -> bool:
1922
uri = request.uri
2023

2124
# Handle list workspaces
22-
if uri.lower() == self.WORKSPACE_URI.lower():
25+
if uri.lower() in self.WORKSPACE_URI:
2326
method = request.method
2427
if method == "GET":
2528
"""https://learn.microsoft.com/en-us/rest/api/fabric/core/workspaces/list-workspaces?tabs=HTTP"""

tests/test_commands/recordings/test_commands/test_deploy/class_setup.yaml

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interactions:
1111
Content-Type:
1212
- application/json
1313
User-Agent:
14-
- ms-fabric-cli/1.5.0 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
14+
- ms-fabric-cli/1.6.1 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
1515
method: GET
1616
uri: https://api.fabric.microsoft.com/v1/workspaces
1717
response:
@@ -26,15 +26,15 @@ interactions:
2626
Content-Encoding:
2727
- gzip
2828
Content-Length:
29-
- '2308'
29+
- '3497'
3030
Content-Type:
3131
- application/json; charset=utf-8
3232
Date:
33-
- Thu, 23 Apr 2026 12:10:26 GMT
33+
- Sun, 05 Jul 2026 10:01:59 GMT
3434
Pragma:
3535
- no-cache
3636
RequestId:
37-
- 75f98b6b-0435-4f24-9c78-1a9295e15538
37+
- 1a6436ae-5d95-42ad-ab3e-2fe3bea576dd
3838
Strict-Transport-Security:
3939
- max-age=31536000; includeSubDomains
4040
X-Content-Type-Options:
@@ -60,7 +60,7 @@ interactions:
6060
Content-Type:
6161
- application/json
6262
User-Agent:
63-
- ms-fabric-cli/1.5.0 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
63+
- ms-fabric-cli/1.6.1 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
6464
method: GET
6565
uri: https://api.fabric.microsoft.com/v1/workspaces
6666
response:
@@ -75,15 +75,15 @@ interactions:
7575
Content-Encoding:
7676
- gzip
7777
Content-Length:
78-
- '2308'
78+
- '3497'
7979
Content-Type:
8080
- application/json; charset=utf-8
8181
Date:
82-
- Thu, 23 Apr 2026 12:10:26 GMT
82+
- Sun, 05 Jul 2026 10:02:00 GMT
8383
Pragma:
8484
- no-cache
8585
RequestId:
86-
- 22096648-c949-4423-8f86-de319415decb
86+
- 9af59919-2aa4-4277-bb78-87d257471f92
8787
Strict-Transport-Security:
8888
- max-age=31536000; includeSubDomains
8989
X-Content-Type-Options:
@@ -109,7 +109,7 @@ interactions:
109109
Content-Type:
110110
- application/json
111111
User-Agent:
112-
- ms-fabric-cli/1.5.0 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
112+
- ms-fabric-cli/1.6.1 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
113113
method: GET
114114
uri: https://api.fabric.microsoft.com/v1/capacities
115115
response:
@@ -125,15 +125,15 @@ interactions:
125125
Content-Encoding:
126126
- gzip
127127
Content-Length:
128-
- '424'
128+
- '462'
129129
Content-Type:
130130
- application/json; charset=utf-8
131131
Date:
132-
- Thu, 23 Apr 2026 12:10:32 GMT
132+
- Sun, 05 Jul 2026 10:02:06 GMT
133133
Pragma:
134134
- no-cache
135135
RequestId:
136-
- bd057165-8274-4a6c-829c-98a985e6b765
136+
- 74ad9216-2c8f-406a-b2c6-408513d2c05b
137137
Strict-Transport-Security:
138138
- max-age=31536000; includeSubDomains
139139
X-Content-Type-Options:
@@ -162,13 +162,14 @@ interactions:
162162
Content-Type:
163163
- application/json
164164
User-Agent:
165-
- ms-fabric-cli/1.5.0 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
165+
- ms-fabric-cli/1.6.1 (None; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
166166
method: POST
167167
uri: https://api.fabric.microsoft.com/v1/workspaces
168168
response:
169169
body:
170-
string: '{"id": "e4f479cd-1e74-4479-bede-829ed8b44206", "displayName": "fabriccli_WorkspacePerTestclass_000001",
171-
"description": "", "type": "Workspace", "capacityId": "00000000-0000-0000-0000-000000000004"}'
170+
string: '{"id": "dfbc98b6-1fac-4969-b12a-0c05046310ee", "displayName": "fabriccli_WorkspacePerTestclass_000001",
171+
"description": "", "type": "Workspace", "capacityId": "00000000-0000-0000-0000-000000000004",
172+
"capacityRegion": "Central US"}'
172173
headers:
173174
Access-Control-Expose-Headers:
174175
- RequestId,Location
@@ -177,17 +178,17 @@ interactions:
177178
Content-Encoding:
178179
- gzip
179180
Content-Length:
180-
- '175'
181+
- '196'
181182
Content-Type:
182183
- application/json; charset=utf-8
183184
Date:
184-
- Thu, 23 Apr 2026 12:10:40 GMT
185+
- Sun, 05 Jul 2026 10:02:13 GMT
185186
Location:
186-
- https://api.fabric.microsoft.com/v1/workspaces/e4f479cd-1e74-4479-bede-829ed8b44206
187+
- https://api.fabric.microsoft.com/v1/workspaces/dfbc98b6-1fac-4969-b12a-0c05046310ee
187188
Pragma:
188189
- no-cache
189190
RequestId:
190-
- 03e03164-a3ed-4451-b4c4-1455f5d05371
191+
- 04a080b4-989e-4e71-960e-e1e93e7e6aea
191192
Strict-Transport-Security:
192193
- max-age=31536000; includeSubDomains
193194
X-Content-Type-Options:
@@ -213,15 +214,16 @@ interactions:
213214
Content-Type:
214215
- application/json
215216
User-Agent:
216-
- ms-fabric-cli/1.5.0 (deploy; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
217+
- ms-fabric-cli/1.6.1 (deploy; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
217218
method: GET
218219
uri: https://api.fabric.microsoft.com/v1/workspaces
219220
response:
220221
body:
221222
string: '{"value": [{"id": "3634a139-2c9e-4205-910b-3b089a31be47", "displayName":
222-
"My workspace", "description": "", "type": "Personal"}, {"id": "e4f479cd-1e74-4479-bede-829ed8b44206",
223+
"My workspace", "description": "", "type": "Personal"}, {"id": "dfbc98b6-1fac-4969-b12a-0c05046310ee",
223224
"displayName": "fabriccli_WorkspacePerTestclass_000001", "description": "",
224-
"type": "Workspace", "capacityId": "00000000-0000-0000-0000-000000000004"}]}'
225+
"type": "Workspace", "capacityId": "00000000-0000-0000-0000-000000000004",
226+
"capacityRegion": "Central US"}]}'
225227
headers:
226228
Access-Control-Expose-Headers:
227229
- RequestId
@@ -230,15 +232,15 @@ interactions:
230232
Content-Encoding:
231233
- gzip
232234
Content-Length:
233-
- '2343'
235+
- '3534'
234236
Content-Type:
235237
- application/json; charset=utf-8
236238
Date:
237-
- Thu, 23 Apr 2026 12:17:02 GMT
239+
- Sun, 05 Jul 2026 10:04:11 GMT
238240
Pragma:
239241
- no-cache
240242
RequestId:
241-
- efb7b8f5-2068-4796-bdfe-58f6191cb283
243+
- c5b6c8ab-de5a-4a3a-89ea-ae99007c7fdd
242244
Strict-Transport-Security:
243245
- max-age=31536000; includeSubDomains
244246
X-Content-Type-Options:
@@ -264,9 +266,9 @@ interactions:
264266
Content-Type:
265267
- application/json
266268
User-Agent:
267-
- ms-fabric-cli/1.5.0 (deploy; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
269+
- ms-fabric-cli/1.6.1 (deploy; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
268270
method: GET
269-
uri: https://api.fabric.microsoft.com/v1/workspaces/e4f479cd-1e74-4479-bede-829ed8b44206/items
271+
uri: https://api.fabric.microsoft.com/v1/workspaces/dfbc98b6-1fac-4969-b12a-0c05046310ee/items
270272
response:
271273
body:
272274
string: '{"value": []}'
@@ -282,11 +284,11 @@ interactions:
282284
Content-Type:
283285
- application/json; charset=utf-8
284286
Date:
285-
- Thu, 23 Apr 2026 12:17:02 GMT
287+
- Sun, 05 Jul 2026 10:04:12 GMT
286288
Pragma:
287289
- no-cache
288290
RequestId:
289-
- c4855104-64c9-487b-954d-205d6989532c
291+
- b5ddfe65-25bc-48f0-b137-9088d284ac16
290292
Strict-Transport-Security:
291293
- max-age=31536000; includeSubDomains
292294
X-Content-Type-Options:
@@ -314,9 +316,9 @@ interactions:
314316
Content-Type:
315317
- application/json
316318
User-Agent:
317-
- ms-fabric-cli/1.5.0 (deploy; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
319+
- ms-fabric-cli/1.6.1 (deploy; Linux/6.6.87.2-microsoft-standard-WSL2; Python/3.12.11)
318320
method: DELETE
319-
uri: https://api.fabric.microsoft.com/v1/workspaces/e4f479cd-1e74-4479-bede-829ed8b44206
321+
uri: https://api.fabric.microsoft.com/v1/workspaces/dfbc98b6-1fac-4969-b12a-0c05046310ee
320322
response:
321323
body:
322324
string: ''
@@ -332,11 +334,11 @@ interactions:
332334
Content-Type:
333335
- application/octet-stream
334336
Date:
335-
- Thu, 23 Apr 2026 12:17:03 GMT
337+
- Sun, 05 Jul 2026 10:04:13 GMT
336338
Pragma:
337339
- no-cache
338340
RequestId:
339-
- f63d5336-33e7-434c-9609-ef62f604a919
341+
- 37e9ad69-f538-4d17-98f1-9713c5abcba3
340342
Strict-Transport-Security:
341343
- max-age=31536000; includeSubDomains
342344
X-Content-Type-Options:

0 commit comments

Comments
 (0)