DEV Community

Mintu Ghosh
Mintu Ghosh

Posted on

The empty folder that would have emptied a production workspace

Deployment tools that support cleanup usually work out what to remove by
comparison: anything present in the target but absent from the source is an
orphan, so delete it.

That rule is fine. It has one input state where it becomes destructive, and that
state is completely normal.

The state

An empty source folder.

If the source has no items, then everything in the target is absent from the
source. Every item in the target workspace is an orphan. Cleanup removes all of
it, reports success, and the deployment then publishes nothing over the top.

Why you will hit it

Not through carelessness. Through sequence.

Onboarding a new solution goes: create the folder, wire up the pipeline, connect
the source workspace, commit the items. Between step one and step four the
folder exists and is empty.

We were sitting in exactly that state across two of three solutions when I
noticed. The folders existed. Each contained one auto-generated placeholder file
and no actual items.

If anything had promoted to those environments in that window, cleanup would
have run against a populated target with an empty source.

The guard

def assert_source_not_empty(directory, solution, environment):
    item_count = count_fabric_items(directory)
    if item_count > 0:
        return item_count

    if read_bool("ALLOW_EMPTY_SOURCE"):
        print("WARNING: {} contains no items, but ALLOW_EMPTY_SOURCE is set."
              .format(directory))
        return 0

    fail(
        "Solution '{}' has no items under {}.\n"
        "Refusing to deploy to {} because orphan removal would treat every "
        "item in the target workspace as an orphan and delete it.\n"
        "Connect the source workspace and commit its items first."
        .format(solution.key, directory, environment)
    )
Enter fullscreen mode Exit fullscreen mode

Three things I would keep if I wrote it again:

It runs before any API call. Not before cleanup — before the SDK is even
loaded. Nothing has authenticated, nothing has been resolved, nothing can
partially happen.

The error says what would have happened, not just what is missing. "No items
found" invites someone to force it through. "Would delete everything in the
target" does not.

There is an override, because emptying a workspace is occasionally what you
want. It is explicit, it is not the default, and it logs loudly.

Testing the dangerous thing on a laptop

This is the one piece of logic whose failure mode is destructive, so I wanted it
provable without touching a real workspace.

The cloud SDK imports were moved behind a function:

def _load_fabric_sdk():
    from azure.identity import ClientSecretCredential
    from fabric_cicd import FabricWorkspace, publish_all_items, ...
    return {...}
Enter fullscreen mode Exit fullscreen mode

Everything up to and including the guard is plain path handling. So the tests
run with neither fabric-cicd nor azure-identity installed:

def test_empty_solution_folder_aborts(self):
    with tempfile.TemporaryDirectory() as tmp:
        empty = Path(tmp) / "Fin"
        empty.mkdir()
        (empty / "Readme.md").write_text("placeholder\n")
        with self.assertRaises(SystemExit) as caught:
            deploy.assert_source_not_empty(empty, self.fin, "UAT")
    self.assertEqual(caught.exception.code, 1)
Enter fullscreen mode Exit fullscreen mode

Note the placeholder file in that test. An empty-looking folder is rarely
literally empty — it usually has a README or a .gitkeep. The count has to be
of real items, not of files, or the guard passes when it should not.

The general question

For any tool that deletes by comparison: what happens when one side of the
comparison is empty?

If the answer is "delete everything", decide whether that state is reachable in
normal use. In our case it was not just reachable, it was a required step in
onboarding.


Full context: [https://www.linkedin.com/pulse/enterprise-microsoft-fabric-cicd-selective-one-azure-devops-ghosh-uxzsf/]
Code: [https://github.com/VEDAFORGE/fabric-cicd-reference/tree/v2.0.0]

Top comments (0)