fs.file
Note
This document describes fs.file module usage.
Synopsis
The fs.file module is your go-to tool for anything file-related. It handles
the obvious stuff — creating empty files, copying them around, deleting them,
and peeking at their metadata. But it also goes deeper: you can ensure a
specific line lives in a config file (and it won’t touch anything if the line
is already there), strip out lines you no longer want, or replace patterns
across the whole file, much like sed but with the comfort of knowing the
module checks first and acts second.
Think of it as a polite sysadmin: it looks before it leaps. If the desired state already exists, it tells you so and moves along. No unnecessary writes, no busted timestamps, no drama.
Usage
The following options are available:
createCreate an empty file with possible content. If
pullis specified, the content comes either from a local path (file:///...) or from the master fileserver.deleteDelete a specified file. In strict mode, returns an error if the file does not exist. In easy mode, it simply reports the fact and carries on.
infoGet file metadata: path, type, size, timestamps, permissions, owner, group and a SHA-256 checksum of the contents. Read-only, never touches the file.
line-presentEnsure a specific line exists in the file. If the line is already there (exact match), the module smiles and moves on. Otherwise it appends the line at the end. If the file does not exist, easy mode creates it with that single line; strict mode returns an error.
line-absentEnsure a specific line is absent from the file. Removes every matching line. If nothing matches, no changes are made. A missing file is considered “line already absent” by definition — no error, no change.
replaceReplace all occurrences of a pattern with a value. Operates on each line independently. If no line contains the pattern, the file is left untouched. In strict mode, a missing file is an error.
All content operations (line-present, line-absent, replace)
follow the inspect-then-act pattern: the file is read first, and the write
happens only when something actually needs to change.
The following keyword arguments are available:
name(type: string, required)A target filename. Required for every operation.
mode(type: string)Can be
strictoreasy(default). In strict mode the module returns a non-zero retcode when the desired state cannot be reached. In easy mode it returns zero with an explanation in the message.pull(type: string)Used with
create. If it starts withfile://the source is a local path. Otherwise it is a filename served by the master’s data fileserver.pattern(type: string)Required for
line-present,line-absentandreplace. The exact line (or substring, forreplace) to match in the file.value(type: string)Required for
replace. The replacement string substituted for every occurrence ofpattern.
Examples
Pull a file from the master fileserver:
actions:
deploy-group-file:
module: fs.file
bind:
- target-host
state:
$:
opts:
- create
args:
name:
- /etc/group
pull:
- /standard/group
Copy a local file:
actions:
backup-group-file:
module: fs.file
bind:
- target-host
state:
$:
opts:
- create
args:
name:
- /backup/etc/group
pull:
- file:///etc/group
Ensure a configuration line is present (e.g. hardening SSH):
actions:
disable-root-login:
module: fs.file
bind:
- target-host
state:
$:
opts:
- line-present
args:
name:
- /etc/ssh/sshd_config
pattern:
- PermitRootLogin no
Remove a deprecated or dangerous configuration line:
actions:
drop-protocol-1:
module: fs.file
bind:
- target-host
state:
$:
opts:
- line-absent
args:
name:
- /etc/ssh/sshd_config
pattern:
- Protocol 1
Replace a port number across an nginx configuration:
actions:
switch-nginx-port:
module: fs.file
bind:
- target-host
state:
$:
opts:
- replace
args:
name:
- /etc/nginx/nginx.conf
pattern:
- listen 80
value:
- listen 8080
Returning Data
create/deleteReturns a message describing the action taken and
data.changed: true.{ "message": "File /etc/group created", "retcode": 0, "data": { "changed": true } }
infoReturns extensive file metadata in the
datasection.{ "data": { "changed": true, "path": "/etc/passwd", "type": "file", "is_file": true, "is_dir": false, "size": 3442, "created": "2023-11-14T15:59:13.966561943+00:00", "modified": "2023-11-14T15:59:13.966561943+00:00", "accessed": "2025-02-13T15:17:01.315542012+00:00", "mode": "0644", "uid": 0, "gid": 0, "user": "root", "group": "root", "sha256": "ee0582f8..." } }
line-presentReturns
changed: truewhen a line was appended,changed: falsewhen the line was already there.{ "retcode": 0, "message": "Line added to /etc/ssh/sshd_config", "data": { "changed": true } }
line-absentReturns
changed: truewhen one or more lines were removed,changed: falsewhen nothing needed removal.{ "retcode": 0, "message": "2 line(s) removed from /etc/ssh/sshd_config", "data": { "changed": true } }
replaceReturns
changed: truewhen substitutions were made,changed: falsewhen the pattern was nowhere to be found.{ "retcode": 0, "message": "3 replacement(s) in /etc/nginx/nginx.conf", "data": { "changed": true } }
Note
In easy mode (the default), operations that find the world already in order
return retcode 0 with changed: false. This means you can run the same
action repeatedly without fear — the second call is a no-op. In strict
mode, a file-not-found or a state that cannot be reached produces retcode 1.