Learn › Linux Automation with Salt › Describe a result, not a command list

Describe a result, not a command list

Describe one managed file, preview and apply the state on minion-a, check the file directly, then recover from deliberate drift.

Describe the result

A teammate edits a file by hand. Another operator appends the same line again. Now the machines disagree. Repeating a command was not the same as maintaining a result.

In this lesson, describe one file's required contents and permissions. Ask Salt to compare that description with one machine, inspect the proposed change, apply it, and verify the actual file. Then repeat the request and investigate drift.

Before you begin: finish Meet and trust your fleet and Ask the right machines. You should recognize a path, use sudo, edit a short text file, and distinguish a target from a returned result. No Python is required. We explain the YAML used here.

Use Practice or Split and the existing fleet terminals. A fresh fleet starts unenrolled. Complete Lesson 1's preparation before continuing. If the old fleet expired, its files are gone; repeat preparation on the new fleet.

Success condition: minion-a has the regular file /var/tmp/linuxcamp-lesson03.txt, containing exactly Managed by Linux Camp. followed by one newline, owned by root:root with mode 0644. Minion-b's path must remain unchanged: absent on our fresh-fleet worked case. This marker is a practice file, not a service-health check or production policy.

Already comfortable with Salt? Read the success condition and the final changed task, attempt them, then use the worked path to investigate gaps. Record which help you used.

A description, a comparison, a change

A state describes a required condition. The file.managed state can ensure a file has specified contents, ownership and permissions. The minion evaluates that condition locally and changes the file if necessary.

An SLS is a Salt state description. We write one YAML document on the master. The master makes it available to the targeted minion; the minion applies it. The file named in the state is on the minion, not on the master.

{"caption":"A simplified state workflow. The SLS describes the minion file; this is not a network-packet diagram.","direction":"vertical","nodes":[{"id":"description","label":"Desired file"},{"id":"comparison","label":"Compare on minion"},{"id":"result","label":"Change if needed"}],"edges":[{"from":"description","to":"comparison","label":"named SLS","kind":"plain","stage":1},{"from":"comparison","to":"result","label":"file.managed","kind":"plain","stage":2}]}

A repeated append command can keep adding lines. A managed-content declaration asks for the same complete contents each time. If the file already meets every declared condition, this state should report no further changes. This property is called idempotence: repeating the operation leaves the intended state the same. It does not mean every Salt operation is harmless to repeat.

Establish the starting evidence

On the master, confirm fresh replies from the two previously verified identities:

sudo salt --timeout=8 -L 'minion-a,minion-b' test.ping

Account for both named replies. If one is missing, return to Lesson 2's investigation before attempting a change. Do not accept new keys merely to make a command work.

In each minion's own terminal, inspect the course path:

sudo ls -ld /var/tmp/linuxcamp-lesson03.txt

On a fresh fleet, the path should not exist. Read the actual message: a permission error is not evidence of absence. If the path already exists, inspect it before continuing. It may be your earlier work; do not overwrite an unexplained file. Repeated practice will have a different starting state from the first run.

Record which machine you inspected and whether the path is absent or already has known course contents. Our first worked example assumes absence on both agents.

Write one small state

On the master, create a directory for this lesson:

sudo install -d -m 0755 /srv/salt/linuxcamp
sudo nano /srv/salt/linuxcamp/lesson03.sls

Enter this complete document, using spaces for indentation:

lesson03_marker:
  file.managed:
    - name: /var/tmp/linuxcamp-lesson03.txt
    - contents: 'Managed by Linux Camp.'
    - contents_newline: True
    - user: root
    - group: root
    - mode: '0644'

In nano, save with Ctrl+O, press Enter to confirm the path, then exit with Ctrl+X. Review what you saved and make it readable by the master's Salt service:

sudo chmod 0644 /srv/salt/linuxcamp/lesson03.sls
sudo cat /srv/salt/linuxcamp/lesson03.sls

lesson03_marker is our state identifier, not a machine name. file.managed is the action that evaluates the required file. name is the destination path on the minion. contents replaces the complete contents; it does not append. contents_newline includes the ending newline. user and group set ownership. Quoting '0644' keeps the permission notation a string: the owner may read and write; the group and others may read.

The fixture uses the master's base state root /srv/salt. The path linuxcamp/lesson03.sls within that root is addressed as linuxcamp.lesson03, without the extension. These files must be readable by the master's Salt service. Creating this SLS does not itself apply it. Do not edit Salt's trust, listeners or management configuration for this task.

Predict before previewing

We intend to change minion-a only. On the master, a named request will be:

sudo salt --timeout=20 'minion-a' state.apply linuxcamp.lesson03 test=True

state.apply linuxcamp.lesson03 selects this SLS. Omitting the name invokes a broader highstate operation; do not omit it. test=True asks the state to report what it expects would change, without applying this file change. Twenty seconds is our collection window, not a guarantee of completion.

Run the preview. Read the minion name, state identifier, Result, Comment and Changes together. For a pending file change, this Salt version uses None as the state result in test mode. None here means a change is predicted, not a completed success or an ordinary failure. A known failure can report False; a matching state can report True with no changes.

In minion-a's own terminal, rerun the path inspection. On our absent-file starting case it must still be absent. Check minion-b too. Record the real preview and observations; do not copy an expected outcome as evidence.

Apply the justified change

Proceed only if the preview describes the intended course file on minion-a and you understand the proposed change. On the master, remove only the test argument:

sudo salt --timeout=20 'minion-a' state.apply linuxcamp.lesson03

Find the named minion and lesson03_marker result. For a successful first creation, expect Result: True and an explanation of the change. Read errors and comments instead of relying on a green summary or the shell's exit status alone.

If you see a missing SLS error, compare the name, path, indentation and file readability with the worked example. A missing return does not prove the operation never ran. Inspect the real minion before deciding whether to retry. Never broaden the target to diagnose one missing result.

Verify independently of the summary

In the minion-a terminal, inspect the actual object:

sudo cat /var/tmp/linuxcamp-lesson03.txt
sudo stat -c '%F %U:%G %a' /var/tmp/linuxcamp-lesson03.txt
sudo od -An -tx1 /var/tmp/linuxcamp-lesson03.txt

cat makes the text readable. stat reports the object type, owner, group and permission digits; compare them with regular file, root:root and 644. A symbolic link is a different object and needs investigation. od shows the actual bytes in hexadecimal. For this ASCII sentence, the final byte must be 0a, the newline. An extra 0a means an extra newline; a different byte sequence means different contents. This small file contains no secret.

In the minion-b terminal, check the same path. It must still be absent if it was absent at the beginning. Connect the evidence to the requirement: correct file on a, no unintended course file on b. Inspecting the SLS on the master alone cannot prove either condition.

Repeat and interpret no changes

Before repeating the named apply on the master, predict its result from the file you just inspected. Then run it again.

The state should report True with no changes when the file still matches. This is stronger evidence than merely repeating a command: you predicted why no new work was needed, inspected a fresh result, and can reconcile it with the actual file.

If it changes again, compare the changed field with the declaration. Another process, a different SLS, or your own edit may have changed the file. Investigate the evidence rather than treating repeated repair as normal.

No-change evidence is scoped to this declaration at this time. It does not certify the whole host's security or reliability. Salt does not continuously undo every edit just because you applied a state once; this lesson invokes the state explicitly.

Create bounded drift, then recover

Drift means the current object no longer matches its declared condition. In the minion-a terminal only, deliberately alter this course-owned file:

printf '%s\n' 'Changed by hand.' | sudo tee /var/tmp/linuxcamp-lesson03.txt
sudo chmod 0600 /var/tmp/linuxcamp-lesson03.txt

Inspect its text and permissions. The master SLS still declares the original sentence and mode 0644. Record that difference before running Salt.

Choose the preview command from the earlier explanation. Check that its changes account for both content and permissions. Verify that previewing did not repair the file. Apply the same named state, inspect the actual result, repeat for no changes, and confirm minion-b's path remains absent.

This repair is not rollback of every side effect on a machine. It restores the properties that this file declaration manages.

Your turn: a changed requirement

The approved requirement now changes: on minion-a only, the course file must contain Reviewed by the fleet team. with one newline and mode 0640, still owned by root:root. Minion-b must remain untouched.

Without copying the entire worked sequence, decide which document to edit and which fields to change. Explain what 0640 permits compared with 0644. Predict the preview, inspect it, apply only the named state, and collect fresh evidence from both agents. Repeat the apply and interpret the result.

Do not edit only the minion file and call that a durable desired-state change: the old SLS would restore its old declaration on the next apply. The approved requirement belongs in the SLS before reconciliation.

If you need help, use the fields explained in Write one small state, then retry the decision with less help. Reading completion and a copied command do not assess your explanation. There is no automatic mastery score for this practice.

Keep the evidence; revisit the mechanism

Save a short note: requirement → intended machine → declaration → prediction → observed state result → independent file evidence → unchanged boundary → remaining uncertainty. Include the actual observations, not just commands or a screenshot of a green summary. Record hints or examples you reopened.

On a later visit, before reopening your notes, consider this changed case: the file's text is correct, but its owner is wrong. Which field and observation matter? Would appending the text help? Explain the smallest justified repair and how you would show that nothing else was targeted. If you have a fresh fleet, complete enrollment before testing it; a written answer is not live evidence.

In the next lesson, this same reasoning will connect configuration to a running service and a useful response. A successfully managed file is one requirement, not the whole operating system.

References: Salt states walkthrough, file.managed, state.apply and test mode, state return structure. Commands are reviewed against the lab's pinned Salt 3006.27 runtime.

Practice Describe a result, not a command list in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.