skip to content

How do you find out what Puppet would change on a node or fleet without letting it change anything, and what are the limits of that report?

level: seniorimportance: nice to knowfreq 42%

answer

  1. compare, then stop before writing
  2. the run is otherwise completely normal
  3. show_diff for the file contents
  4. only what the catalog declares
  5. guards still execute, commands do not

basics

~20 s

Run the agent in no-op mode — puppet agent -t --noop, or noop = true in puppet.conf. Puppet still compiles the catalog and inspects every managed resource, but reports out-of-sync ones instead of correcting them. It says nothing about resources the catalog does not declare.

solid answer

~50 s

No-op mode is Puppet's dry run. With `--noop` on the command line, `noop = true` in `puppet.conf`, or the `noop` metaparameter on an individual resource, the agent does a completely normal run — facts up, catalog compiled on the server, every managed resource inspected through its provider — but instead of writing changes it records what it *would* have done. Add `--show_diff` and you get the actual file content differences. The report goes to the server and PuppetDB like any other, so you can run a no-op pass across the fleet and query which nodes have pending changes. Two limits matter. First, it only sees what the catalog manages: anything nobody wrote a resource for is invisible, so a clean no-op run is not proof the node is correct. Second, cascading effects are unknowable — a package that would have been installed was not, so resources downstream of it can only be reported speculatively.

code

bash · 5 lines
bash
# Dry run one node and show the file content differences
sudo puppet agent -t --noop --show_diff

# Same for a masterless setup
sudo puppet apply --noop --show_diff /etc/puppetlabs/code/environments/production/manifests/site.pp

go deeper

for a junior

Know that --noop exists and what it does: a normal run that reports what would change instead of changing it, with --show_diff to see file differences.

for a middle

Explain that the catalog is still compiled and providers still read live state — only the write is suppressed — and that individual resources can override the run-level setting with the noop metaparameter.

for a senior

Bring the limits without prompting: unmanaged resources are invisible, cascading effects cannot be simulated, exec guards still execute, and a node left in no-op silently stops being enforced.

for a principal

Treat it as a change-control instrument — canary group in no-op against a candidate environment, reports queried fleet-wide from PuppetDB — while owning the risk that no-op is also the easiest way to quietly disable enforcement across an estate.

## Turning enforcement off without turning inspection off Puppet's apply step is already a compare-then-write: for each resource, the provider is asked what the current state of each managed property is, and only differing properties are written. No-op mode simply stops at the compare. Everything before it still happens — Facter runs, facts go to the server, the catalog is compiled, the graph is walked, providers read live state. What changes is that the agent logs and reports each out-of-sync resource as a `noop` event rather than correcting it. There are three places to switch it on, and they nest: ```bash # One run, right now, showing file content diffs sudo puppet agent -t --noop --show_diff ``` ```ini # /etc/puppetlabs/puppet/puppet.conf — every run on this node is no-op [agent] noop = true ``` ```puppet # Per resource, in the manifest service { 'nginx': ensure => running, noop => false, } ``` The per-resource `noop` metaparameter overrides the run-level setting in both directions: `noop => true` makes one resource report-only during an otherwise enforcing run, and `noop => false` makes one resource apply for real even during a no-op run. That second form is how a module author keeps something safety-critical enforced while a team evaluates the rest of a change. The `-t` (`--test`) flag is worth knowing separately: it bundles the options you want for a one-off interactive run — a single foreground run, verbose output, no splay — so `puppet agent -t --noop` is the standard "tell me what you'd do, now" invocation. ## What you legitimately use it for **Reviewing a change before it enforces.** Pin a canary node group to the candidate environment, run no-op there, read the report. This is the closest Puppet gets to a preview of a code change against real machines, and it is far more informative than a syntax check because it runs against actual node state. **A fleet-wide "what is out of sync" pass.** Because no-op runs post normal reports, and reports land in PuppetDB, you can query across the fleet for nodes with pending changes rather than reading logs node by node. **Bringing a legacy machine under management.** Point a catalog at a hand-built server in no-op first. The report is the inventory of everything you are about to overwrite, which is precisely the list you want before the first enforcing run. ## The limits, which are the interesting half of the answer **It only knows what you declared.** Puppet is not a whole-machine auditor. A resource nobody wrote does not exist as far as the report is concerned — an extra cron entry, a stray package, a firewall rule added by hand. A completely clean no-op run means "everything I manage matches", not "this node is correct". Confusing the two is the classic misreading. **Cascading effects cannot be simulated.** Because nothing was written, the run cannot observe what would have followed. A package that would have been installed was not, so the config file templated against it, the service that would have been refreshed, and any `exec` gated on its presence are all reported against a node state that never came into being. Expect the first real run to produce more events than the no-op run predicted, especially on a node that is far from desired state. **`exec` behaves specially and it matters.** In no-op mode an `exec` resource does not run its command — but its `onlyif`, `unless` and `creates` guard conditions *do* execute, because that is how Puppet decides whether it would have run. A guard with side effects therefore still has them during a "dry run". Guards should be cheap, read-only checks; this is one of the concrete reasons why. **It is a report, not a lock.** Setting `noop = true` on a node stops enforcement, and stopping enforcement is not free: the node quietly drifts for as long as it stays that way, while still reporting in and looking healthy on a dashboard. No-op left on "temporarily" during an incident is a genuinely common way to end up with a fleet nobody is enforcing. **Exit codes need checking against your version before you gate on them.** `--detailed-exitcodes` distinguishes "nothing to do" from "changes" and "failures", which is what a CI drift-check job keys on — but confirm how your Puppet version reports a no-op run's pending changes before wiring a pipeline to fail on it.

  • A no-op run reports zero pending changes. Is the node correct?
    Only for the resources the catalog declares. Puppet has no opinion about anything you never wrote a resource for — an unmanaged package, a hand-added cron entry, a firewall rule. A clean report means "everything I manage matches", which is a much narrower claim than "this node is compliant". Real compliance checking needs coverage of what *should not* exist, which is a different job from enforcing what should.
  • Why can the first enforcing run after a clean-looking no-op produce more events than expected?
    Because no-op cannot observe cascading effects. Nothing was actually written, so a package that would have been installed was not, and every resource downstream of it — the config file templated from it, the service refresh, an `exec` gated on its presence — was evaluated against a node state that never existed. The further a node is from desired state, the less predictive its no-op report is.
  • Does `--noop` guarantee that nothing at all executes on the node?
    No. `exec` resources do not run their commands, but their `onlyif`, `unless` and `creates` guards do, because Puppet has to evaluate them to decide whether the command would have run. A guard that mutates something therefore still mutates it during a dry run. Keep guard commands cheap and read-only — this is a concrete reason for the rule, not a stylistic one.

saying these in an interview costs you the question

  • Treats a clean no-op run as proof the node is compliant
  • Thinks no-op skips catalog compilation entirely
  • Believes no-op guarantees nothing executes on the node
  • Leaves noop = true set on nodes after an incident
  • Expects the real run to match the no-op report exactly

context