Skip to content

File

File describes the content and metadata of one file.

datum: v1alpha1
type: File

name: nginx-config

requires:
  - Package[nginx]

desired:
  path: /etc/nginx/nginx.conf
  owner: root
  group: root
  mode: "0640"
  source: files/nginx.conf

Target identity is desired.path and not name, so two File resources with different names and the same path are a conflict.

Fields

Field Type Required Meaning
path absolute path Yes The file being managed.
state present, absent No, defaults to present Whether the file should exist.
content string No Literal content, held in the resource.
source path No Content taken from a file in the repository, verbatim.
template path No Content taken from a file in the repository and rendered.
secretRef string No The whole content is a secret resolved on the host.
owner string No Owning user name.
group string No Owning group name.
mode string No Permission bits, quoted.
sensitive boolean No, defaults to false Suppresses rendering of the content anywhere.
validate string No Names a validator run against the staged content before it goes live.
allowPrivileged boolean No, defaults to false Permits a mode that grants privilege.

content, source, template and secretRef are mutually exclusive, and a resource setting more than one is rejected. Setting none of them manages metadata only, which is how ownership or permissions on a file created by a package are corrected without taking over what is in it.

template differs from source only in that label values are substituted as the content is read. Keeping them as separate fields rather than one field with a flag means whether a file is rendered is visible on the line that names it.

source is resolved relative to the layer directory, so a resource in fleet/roles/web/ referring to files/nginx.conf means fleet/roles/web/files/nginx.conf. It must be a relative path and must resolve to somewhere inside the fleet root, which is confinement, not a convention.

sensitive: true stops the content appearing in a diff, a plan or a report, while ownership and mode are still reported normally. It is not secret management, because the content still sits in the repository in plain text, and the limitation needs reading before anything relies on it.

Quoting mode

mode is a string because YAML would otherwise interpret the value.

mode: "0640"    # correct
mode: 0640      # wrong

Unquoted, 0640 is a number, and depending on the YAML version parsing it is either octal 416 or decimal 640. Requiring a string removes the ambiguity, and a mode given as a number is rejected instead of guessed at.

Fields not set are not managed

Only the fields the resource declares are compared. A resource setting mode and not owner corrects permissions and leaves ownership alone, whatever it is.

This follows from Datum managing what is declared, and it is what allows a file to be partially owned. It also means an omission is indistinguishable from an intention, so a file whose ownership matters needs ownership declared even when it currently looks right.

Observation

Field Reported
exists Whether the path exists and is a regular file.
owner Owning user name.
group Owning group name.
mode Permission bits.
content A digest of the content, not the content itself.

Reporting a digest keeps observation cheap on large files and makes the comparison exact. Rendering a readable difference happens when a plan is displayed, which is a separate concern from measuring state.

A path that exists but is a directory or a symlink reports exists: false for the purposes of this resource, along with what was actually found, so the plan can say that the target is occupied by something else rather than reporting a content mismatch.

Applying

Content is written to a temporary file in the same directory and renamed into place, so a process reading the file sees either the previous content or the new content. Ownership and mode are set on the temporary file before the rename, which avoids a window where the file exists with the wrong permissions.

Writing to the same directory matters because a rename across filesystems is not atomic, and /tmp is frequently a different filesystem from /etc.

Security consideration

A provider writing as root has to assume the path is hostile. An unprivileged user who can create entries in the target directory, which is common under /var/lib and /srv, can replace the target with a symbolic link and have root write somewhere else. The handling that follows from this, covering O_NOFOLLOW, exclusive temporary file creation, setting metadata on the file descriptor and not the path, and hard link counts, is specified in the threat model.

Verification

The file is re-read and every declared field compared. The most common verification failure is content that does not match because the provider normalised it on write, usually by adding or removing a trailing newline, which produces a resource that appears to change on every pass.

Open questions

Whether owner and group should accept numeric ids alongside names is undecided. Names are clearer and depend on the user existing, which is a dependency the manifest can express, and ids avoid that ordering problem while being harder to read.