saltstack / saltstack/salt

[FEATURE REQUEST] Provide `file.absent` with `removedirs` option

Open
#61,330 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
15.7k
Forks
5.6k
Avg merge
2d 44m
Merged PRs (30d)
80

Description

Is your feature request related to a problem? Please describe.

file.managed does support makedirs: True for creating the directories need to write a file. When that file is no longer needed, file.absent can remove that file, but all directories are kept.

Describe the solution you'd like

I'd like to have a removedirs: True option on file.absent that works like Python's os.removedirs() function and removes all empty parent directories after removing the file.

/path/to/file.txt:
  file.absent:
    - removedirs: True

This also looks very symmetrical to file.managed with makedirs: True.

Describe alternatives you've considered

The most common solution mentioned is using file.absent on the parent directory with unless: and some glob check, but that has many edge cases. Just running a /path/to/* glob often will not detect .hidden files, and it only works for a single directory level. A custom state could be added, but needs to be maintained separately:

# vim: ft=python:sw=4

import os

def _each_parent(name):
    head, tail = os.path.split(name)
    if not tail:
        head, tail = os.path.split(head)

    while head and tail:
        yield head
        head, tail = os.path.split(head)

def absent(name, removedirs=True):
    """
    Ensures the given file path is removed, and all empty parent directories are
    removed too.
    """

    ret = __states__["file.absent"](name)

    if "removed" in ret["changes"]:
        ret["changes"]["removed"] = [ret["changes"]["removed"]]

    if not removedirs or ret["result"] == False:
        return ret

    # Clean up empty parent directories
    for path in _each_parent(name):
        try:
            if not os.path.isdir(path) or len(os.listdir(path)) > 0:
                break

            if "removed" not in ret["changes"]:
                ret["changes"]["removed"] = []

            ret["changes"]["removed"].append(path)

            if __opts__["test"]:
                ret["result"] = None
            else:
                os.rmdir(path)
        except OSError:
            break

    return ret

(This one does not change the comment, but extends the state diff with deleted parent directories.)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the file.absent and file.managed state entry points and compare the requested behavior with Python's os.removedirs() and the custom absent state shown in the issue. Done means file.absent accepts removedirs: True, removes empty parent directories after the target, and preserves existing behavior when the option is not enabled.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devops, infrastructure
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.