Salt States can aggressively manipulate files on a system. There are a number of ways in which files can be managed.
Regular files can be enforced with the managed function. This function downloads files from the salt master and places them on the target system. The downloaded files can be rendered as a jinja, mako, or wempy template, adding a dynamic component to file management. An example of file.managed which makes use of the jinja templating system would look like this:
/etc/http/conf/http.conf:
file.managed:
- source: salt://apache/http.conf
- user: root
- group: root
- mode: 644
- template: jinja
- defaults:
custom_var: "default value"
other_var: 123
{% if grains['os'] == 'Ubuntu' %}
- context:
custom_var: "override"
{% endif %}
Note
When using both the defaults and context arguments, note the extra indentation (four spaces instead of the normal two). This is due to an idiosyncrasy of how PyYAML loads nested dictionaries, and is explained in greater detail here.
If using a template, any user-defined template variables in the file defined in source must be passed in using the defaults and/or context arguments. The general best practice is to place default values in defaults, with conditional overrides going into context, as seen above.
The source parameter can be specified as a list. If this is done, then the first file to be matched will be the one that is used. This allows you to have a default file on which to fall back if the desired file does not exist on the salt fileserver. Here's an example:
/etc/foo.conf:
file.managed:
- source:
- salt://foo.conf.{{ grains['fqdn'] }}
- salt://foo.conf.fallback
- user: foo
- group: users
- mode: 644
- backup: minion
Note
Salt supports backing up managed files via the backup option. For more details on this functionality please review the backup_mode documentation.
The source parameter can also specify a file in another Salt environment. In this example foo.conf in the dev environment will be used instead.
/etc/foo.conf:
file.managed:
- source:
- salt://foo.conf?saltenv=dev
- user: foo
- group: users
- mode: '0644'
Warning
When using a mode that includes a leading zero you must wrap the value in single quotes. If the value is not wrapped in quotes it will be read by YAML as an integer and evaluated as an octal.
Special files can be managed via the mknod function. This function will create and enforce the permissions on a special file. The function supports the creation of character devices, block devices, and fifo pipes. The function will create the directory structure up to the special file if it is needed on the minion. The function will not overwrite or operate on (change major/minor numbers) existing special files with the exception of user, group, and permissions. In most cases the creation of some special files require root permisisons on the minion. This would require that the minion to be run as the root user. Here is an example of a character device:
/var/named/chroot/dev/random:
file.mknod:
- ntype: c
- major: 1
- minor: 8
- user: named
- group: named
- mode: 660
Here is an example of a block device:
/var/named/chroot/dev/loop0:
file.mknod:
- ntype: b
- major: 7
- minor: 0
- user: named
- group: named
- mode: 660
Here is an example of a fifo pipe:
/var/named/chroot/var/log/logfifo:
file.mknod:
- ntype: p
- user: named
- group: named
- mode: 660
Directories can be managed via the directory function. This function can create and enforce the permissions on a directory. A directory statement will look like this:
/srv/stuff/substuf:
file.directory:
- user: fred
- group: users
- mode: 755
- makedirs: True
If you need to enforce user and/or group ownership or permissions recursively on the directory's contents, you can do so by adding a recurse directive:
/srv/stuff/substuf:
file.directory:
- user: fred
- group: users
- mode: 755
- makedirs: True
- recurse:
- user
- group
- mode
As a default, mode will resolve to dir_mode and file_mode, to specify both directory and file permissions, use this form:
/srv/stuff/substuf:
file.directory:
- user: fred
- group: users
- file_mode: 744
- dir_mode: 755
- makedirs: True
- recurse:
- user
- group
- mode
Symlinks can be easily created; the symlink function is very simple and only takes a few arguments:
/etc/grub.conf:
file.symlink:
- target: /boot/grub/grub.conf
Recursive directory management can also be set via the recurse function. Recursive directory management allows for a directory on the salt master to be recursively copied down to the minion. This is a great tool for deploying large code and configuration systems. A state using recurse would look something like this:
/opt/code/flask:
file.recurse:
- source: salt://code/flask
- include_empty: True
A more complex recurse example:
{% set site_user = 'testuser' %}
{% set site_name = 'test_site' %}
{% set project_name = 'test_proj' %}
{% set sites_dir = 'test_dir' %}
django-project:
file.recurse:
- name: {{ sites_dir }}/{{ site_name }}/{{ project_name }}
- user: {{ site_user }}
- dir_mode: 2775
- file_mode: '0644'
- template: jinja
- source: salt://project/templates_dir
- include_empty: True
Verify that the named file or directory is absent, this will work to reverse any of the functions in the file state module.
Prepare accumulator which can be used in template in file.managed state. Accumulator dictionary becomes available in template. It can also be used in file.blockreplace.
Example:
Given the following:
animals_doing_things:
file.accumulated:
- filename: /tmp/animal_file.txt
- text: ' jumps over the lazy dog.'
- require_in:
- file: animal_file
animal_file:
file.managed:
- name: /tmp/animal_file.txt
- source: salt://animal_file.txt
- template: jinja
One might write a template for animal_file.txt like the following:
The quick brown fox{% for animal in accumulator['animals_doing_things'] %}{{ animal }}{% endfor %}
Collectively, the above states and template file will produce:
The quick brown fox jumps over the lazy dog.
Multiple accumulators can be "chained" together.
Note
The 'accumulator' data structure is a Python dictionary. Do not expect any loop over the keys in a deterministic order!
Ensure that some text appears at the end of a file
The text will not be appended again if it already exists in the file. You may specify a single line of text or a list of lines to append.
Multi-line example:
/etc/motd:
file.append:
- text: |
Thou hadst better eat salt with the Philosophers of Greece,
than sugar with the Courtiers of Italy.
- Benjamin Franklin
Multiple lines of text:
/etc/motd:
file.append:
- text:
- Trust no one unless you have eaten much salt with him.
- "Salt is born of the purest of parents: the sun and the sea."
Gather text from multiple template files:
/etc/motd:
file:
- append
- template: jinja
- sources:
- salt://motd/devops-messages.tmpl
- salt://motd/hr-messages.tmpl
- salt://motd/general-messages.tmpl
New in version 0.9.5.
Maintain an edit in a file in a zone delimited by two line markers
New in version 2014.1.0.
A block of content delimited by comments can help you manage several lines entries without worrying about old entries removal. This can help you maintaining an un-managed file containing manual edits. Note: this function will store two copies of the file in-memory (the original version and the edited version) in order to detect changes and only edit the targeted file if necessary.
Parameters: |
|
---|---|
Return type: | bool or str Example of usage with an accumulator and with a variable: {% set myvar = 42 %}
hosts-config-block-{{ myvar }}:
file.blockreplace:
- name: /etc/hosts
- marker_start: "# START managed zone {{ myvar }} -DO-NOT-EDIT-"
- marker_end: "# END managed zone {{ myvar }} --"
- content: 'First line of content'
- append_if_not_found: True
- backup: '.bak'
- show_changes: True
hosts-config-block-{{ myvar }}-accumulated1:
file.accumulated:
- filename: /etc/hosts
- name: my-accumulator-{{ myvar }}
- text: "text 2"
- require_in:
- file: hosts-config-block-{{ myvar }}
hosts-config-block-{{ myvar }}-accumulated2:
file.accumulated:
- filename: /etc/hosts
- name: my-accumulator-{{ myvar }}
- text: |
text 3
text 4
- require_in:
- file: hosts-config-block-{{ myvar }}
|
will generate and maintain a block of content in /etc/hosts:
# START managed zone 42 -DO-NOT-EDIT-
First line of content
text 2
text 3
text 4
# END managed zone 42 --
Comment out specified lines in a file.
The file will be backed up before edit with this file extension
Warning
This backup will be overwritten each time sed / comment / uncomment is called. Meaning the backup will only be useful after the first invocation.
Usage:
/etc/fstab:
file.comment:
- regex: ^bind 127.0.0.1
New in version 0.9.5.
If the source file exists on the system, copy it to the named file. The named file will not be overwritten if it already exists unless the force option is set to True.
Ensure that a named directory is present and has the right perms
Enforce user/group ownership and mode of directory recursively. Accepts a list of strings representing what you would like to recurse. Example:
/var/log/httpd:
file.directory:
- user: root
- group: root
- dir_mode: 755
- file_mode: 644
- recurse:
- user
- group
- mode
If the desired path is a symlink (or recurse is defined and a symlink is encountered while recursing), follow it and check the permissions of the directory/file to which the symlink points.
New in version 2014.1.4.
Verify that the named file or directory is present or exists. Ensures pre-requisites outside of Salt's purview (e.g., keytabs, private keys, etc.) have been previously satisfied before deployment.
Manage a given file, this function allows for a file to be downloaded from the salt master and potentially run through a templating system.
The source file to download to the minion, this source file can be hosted on either the salt master server, or on an HTTP or FTP server. For files hosted on the salt file server, if the file is located on the master in the directory named spam, and is called eggs, the source string is salt://spam/eggs. If source is left blank or None (use ~ in YAML), the file will be created as an empty file and the content will not be managed
If the file is hosted on a HTTP or FTP server then the source_hash argument is also required
The function accepts the first encountered long unbroken alphanumeric string of correct length as a valid hash, in order from most secure to least secure:
Type Length
====== ======
sha512 128
sha384 96
sha256 64
sha224 56
sha1 40
md5 32
The file can contain several checksums for several files. Each line must contain both the file name and the hash. If no file name is matched, the first hash encountered will be used, otherwise the most secure hash with the correct source file name will be used.
Debian file type *.dsc is supported.
Examples:
/etc/rc.conf ef6e82e4006dee563d98ada2a2a80a27
sha254c8525aee419eb649f0233be91c151178b30f0dff8ebbdcc8de71b1d5c8bcc06a /etc/resolv.conf
ead48423703509d37c4a90e6a0d53e143b6fc268
If the remote server URL has the hash file as an apparent sub-directory of the source file, the module will discover that it has already cached a directory where a file should be cached. For example:
tomdroid-src-0.7.3.tar.gz:
file.managed:
- name: /tmp/tomdroid-src-0.7.3.tar.gz
- source: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz
- source_hash: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz/+md5
New in version 0.17.0.
Operates like contents, but draws from a value stored in pillar, using the pillar path syntax used in pillar.get. This is useful when the pillar value contains newlines, as referencing a pillar variable using a jinja/mako template can result in YAML formatting issues due to the newlines causing indentation mismatches.
For example, the following could be used to deploy an SSH private key:
/home/deployer/.ssh/id_rsa:
file.managed:
- user: deployer
- group: deployer
- mode: 600
- contents_pillar: userdata:deployer:id_rsa
This would populate /home/deployer/.ssh/id_rsa with the contents of pillar['userdata']['deployer']['id_rsa']. An example of this pillar setup would be like so:
userdata:
deployer:
id_rsa: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoQiwO3JhBquPAalQF9qP1lLZNXVjYMIswrMe2HcWUVBgh+vY
U7sCwx/dH6+VvNwmCoqmNnP+8gTPKGl1vgAObJAnMT623dMXjVKwnEagZPRJIxDy
B/HaAre9euNiY3LvIzBTWRSeMfT+rWvIKVBpvwlgGrfgz70m0pqxu+UyFbAGLin+
GpxzZAMaFpZw4sSbIlRuissXZj/sHpQb8p9M5IeO4Z3rjkCP1cxI
-----END RSA PRIVATE KEY-----
Note
The private key above is shortened to keep the example brief, but shows how to do multiline string in YAML. The key is followed by a pipe character, and the mutli-line string is indented two more spaces.
Verify that the named file or directory is missing, this returns True only if the named file is missing but does not remove the file if it is present.
Create a special file similar to the 'nix mknod command. The supported device types are p (fifo pipe), c (character device), and b (block device). Provide the major and minor numbers when specifying a character device or block device. A fifo pipe does not require this information. The command will create the necessary dirs if needed. If a file of the same name not of the same type/major/minor exists, it will not be overwritten or unlinked (deleted). This is logically in place as a safety measure because you can really shoot yourself in the foot here and it is the behavior of 'nix mknod. It is also important to note that not just anyone can create special devices. Usually this is only done as root. If the state is executed as none other than root on a minion, you may receive a permission error.
Usage:
/dev/chr:
file.mknod:
- ntype: c
- major: 180
- minor: 31
- user: root
- group: root
- mode: 660
/dev/blk:
file.mknod:
- ntype: b
- major: 8
- minor: 999
- user: root
- group: root
- mode: 660
/dev/fifo:
file.mknod:
- ntype: p
- user: root
- group: root
- mode: 660
New in version 0.17.0.
Apply a patch to a file. Note: a suitable patch executable must be available on the minion when using this state function.
Usage:
# Equivalent to ``patch --forward /opt/file.txt file.patch``
/opt/file.txt:
file.patch:
- source: salt://file.patch
- hash: md5=e138491e9d5b97023cea823fe17bac22
Recurse through a subdirectory on the master and copy said subdirectory over to the specified path.
Note
The template option is required when recursively applying templates.
When copying, include only this pattern from the source. Default is glob match; if prefixed with 'E@', then regexp match. Example:
- include_pat: hello* :: glob matches 'hello01', 'hello02'
... but not 'otherhello'
- include_pat: E@hello :: regexp matches 'otherhello',
'hello01' ...
Exclude this pattern from the source when copying. If both include_pat and exclude_pat are supplied, then it will apply conditions cumulatively. i.e. first select based on include_pat, and then within that result apply exclude_pat.
Also, when 'clean=True', exclude this pattern from the removal list and preserve in the destination. Example:
- exclude_pat: APPDATA* :: glob matches APPDATA.01,
APPDATA.02,.. for exclusion
- exclude_pat: E@(APPDATA)|(TEMPDATA) :: regexp matches APPDATA
or TEMPDATA for exclusion
When copying, only copy paths which are of depth maxdepth from the source path. Example:
- maxdepth: 0 :: Only include files located in the source
directory
- maxdepth: 1 :: Only include files located in the source
or immediate subdirectories
If the source file exists on the system, rename it to the named file. The named file will not be overwritten if it already exists unless the force option is set to True.
Maintain an edit in a file
New in version 0.17.0.
Params are identical to replace().
Deprecated since version 0.17.0: Use replace() instead.
Maintain a simple edit to a file
The file will be searched for the before pattern before making the edit. In general the limit pattern should be as specific as possible and before and after should contain the minimal text to be changed.
Negate the search command (!)
New in version 0.17.0.
Usage:
# Disable the epel repo by default
/etc/yum.repos.d/epel.repo:
file.sed:
- before: 1
- after: 0
- limit: ^enabled=
# Remove ldap from nsswitch
/etc/nsswitch.conf:
file.sed:
- before: 'ldap'
- after: ''
- limit: '^passwd:'
New in version 0.9.5.
Serializes dataset and store it into managed file. Useful for sharing simple configuration files.
Write the data as this format. Supported output formats:
Create parent directories for destination file.
New in version 2014.1.3.
For example, this state:
/etc/dummy/package.json:
file.serialize:
- dataset:
name: naive
description: A package using naive versioning
author: A confused individual <iam@confused.com>
dependencies:
express: >= 1.2.0
optimist: >= 0.1.0
engine: node 0.4.1
- formatter: json
will manages the file /etc/dummy/package.json:
{
"author": "A confused individual <iam@confused.com>",
"dependencies": {
"express": ">= 1.2.0",
"optimist": ">= 0.1.0"
},
"description": "A package using naive versioning",
"engine": "node 0.4.1"
"name": "naive",
}
Create a symlink
If the file already exists and is a symlink pointing to any location other than the specified target, the symlink will be replaced. If the symlink is a regular file or directory then the state will return False. If the regular file or directory is desired to be replaced with a symlink pass force: True, if it is to be renamed, pass a backupname.
Replicate the 'nix "touch" command to create a new empty file or update the atime and mtime of an existing file.
Note that if you just want to create a file and don't care about atime or mtime, you should use file.managed instead, as it is more feature-complete. (Just leave out the source/template/contents arguments, and it will just create the file and/or check its permissions, without messing with contents)
Usage:
/var/log/httpd/logrotate.empty:
file.touch
New in version 0.9.5.
Uncomment specified commented lines in a file
Usage:
/etc/adduser.conf:
file.uncomment:
- regex: EXTRA_GROUPS
New in version 0.9.5.
Current Salt release: 2014.1.6
Docs for previous releases on salt.rtfd.org.