Skip to content

    Dry runs

    A dry run connects to every target host and reports what each step would do there, without changing anything. Here is what it can prove and what it cannot.

    A dry run executes a plan with every write suppressed. It resolves the same variables, connects to the same hosts with the same credentials, and reports what each step would do on each of them. Nothing is created, changed or removed.

    It is a separate execution mode, chosen when you start the run, and it is the mode to reach for the first time a plan meets a host you care about.

    Two kinds of answer#

    A dry run does not give one kind of answer, because it honestly cannot. Every step comes back at one of two levels, and the report labels which.

    Level 1, shown but not simulated. For a step whose effect Opafra cannot predict, the report shows you the real thing that would run: the fully resolved command, or the body of the script that would be uploaded, with every variable substituted and every secret redacted. It is marked will run (not simulated). You are reviewing the actual text, and the judgement about what it does is yours.

    Level 2, simulated against the host. For a step calling a tool that supports a read-only check, Opafra runs that check on the target and reports a verdict: would create, would change, no change, or unknown. Where a change is a file edit, the report carries the diff.

    What supports a real check#

    These tools probe the host with a read-only command and report a verdict:

    ToolWhat it checksHow
    apt.install, yum.install, nginx.installIs each package already installeddpkg -s / rpm -q
    systemd.start, systemd.restart, systemd.enableCurrent service statesystemctl is-active / is-enabled
    user.create, user.deleteDoes the account existid
    ufw.allow, ufw.enableCurrent rule set and statusufw status
    file.lineinfileRenders the edit against the current filereads the file, diffs it
    cron.addIs that exact line already in the crontabcrontab -l
    file.backupDoes the source exist, would the destination be overwrittenstat
    nginx.reloadIs the config valid before anything reloadsnginx -t
    template.deployRenders the template and diffs it against the live filereads the file, diffs it

    Everything else, including ssh.cmd and uploaded scripts, is level 1. If a probe fails, that step degrades to level 1 rather than guessing: you still see exactly what would run.

    Reading the report#

    The report groups by step and then by host, because the whole reason to dry run against more than one machine is that they disagree.

    dry run, three hosts
    Step 2  Ensure nginx is installed          apt.install
      web-01   no change      nginx 1.24.0-2 already installed
      web-02   would create   nginx not installed
      web-03   no change      nginx 1.24.0-2 already installed
    
    Step 3  Deploy the site config             template.deploy
      web-01   would change   /etc/nginx/sites-enabled/app.conf   +4 -2
      web-02   would create   /etc/nginx/sites-enabled/app.conf
      web-03   no change
    
    Step 4  Reload nginx                       ssh.cmd
      all      will run (not simulated)
               sudo systemctl reload nginx

    web-02 is the host that will actually be touched. That is the finding, and it is visible before anything happens rather than in the middle of the real run.

    The report also lists any {{ vars.x }} that stayed unresolved after substitution. Those will be empty at run time, which is almost never what was intended, and a dry run is the cheap place to discover it.

    Secrets in a preview#

    A dry run prints resolved commands, so it redacts aggressively before printing. Any secret:// reference is masked, and so is the value of any argument whose name looks like a credential. The same masking runs over rendered file contents in a diff, so deploying a config full of passwords does not leak them into the report.

    The redaction deliberately over-matches. Hiding something that was not a secret is a smaller problem than printing one that was.

    What a dry run does not prove#

    It is a preview, not a guarantee. Four honest limits:

    • The host can change between the preview and the run. Another process installs the package, someone edits the file. The verdict was true when it was taken.
    • Level 1 steps are unpredicted by design. A dry run cannot tell you what ssh.cmd: ./deploy.sh will do, and it does not pretend to. It shows you the script.
    • A step's effect on a later step is not modelled. Step 3 is previewed against the host as it is now, not as step 2 would have left it. Where a step creates the file the next one edits, the second preview will say the file is missing.
    • Failures do not surface. No step actually executes, so nothing fails and nothing halts. A real run stops at the first failed step; a dry run reports on all of them.

    When to dry run#

    Dry run when the plan is new, when the target list grew, when the plan changed since it last ran, or when the environment is one where a mistake is expensive. Skip it for a plan that has run unchanged against the same hosts for months, where the preview would tell you what you already know.

    If a plan is mostly raw commands, most of its report will be level 1, and the value drops to "read this text before it runs". That is still worth ten seconds. It is also a signal that the plan would be better written against tools, which can be previewed properly.

    Next steps#