Skip to content

line

line manages one line in a file that something else owns, such as a setting in the configuration file a package installs, and leaves the rest of the file alone. When the file would change, plan shows the change as a diff of the file, and apply writes it in one rename, keeping the file's mode and owner.

Use file when sadame should own the whole file; use line when the package should keep owning it and you only need one setting.

New in 0.2.0

Minimal example

examples/resources/line/minimal.rb
line "export EDITOR=vi", path: "/etc/profile"

Options

Option Type Default Description
path String required Absolute path of the file to edit.
line String the name The line, without a newline. Give it when the name is a description, or when the same line goes into two files.
match Regexp, or a String with one none Lines to replace. Every line that matches is replaced by the line; when none matches and the line is not in the file, it is appended.
state :present or :absent :present :absent removes the line, and every line that matches match.
verify String containing %{path} none A command that checks the new content before it replaces the file, such as "sshd -t -f %{path}". If it fails, the file is not changed.
sensitive true or false false Hide the diff in output and JSON, for lines with secrets.
notifies [:action, "type[name]"], optionally with :immediately, or a list of them none Actions to run on other resources when this one changes. See Notifications.
subscribes [:action, "type[name]"], optionally with :immediately, or a list of them none Actions to run on this resource when another one changes.
tags Symbol, String, or an Array of them none Tags for running part of a recipe with --tags and --skip-tags. See Tags.

Without match, the line is appended when no line in the file is exactly the same. Lines are compared without their newline; spaces count.

The file must exist when the change is applied: line edits files, it does not create them. On a machine where the file comes from a package that the same recipe installs, plan shows the line being added to a file that "does not exist yet", and apply edits the file the package created.

Scenarios

examples/resources/line/recipe.rb
# The SSH server's configuration belongs to its package. Each line resource
# changes one line and leaves the rest of the file as it was shipped, and
# sshd -t checks the edited file before it replaces the real one.
package({ "arch" => "openssh" }.fetch(platform.family, "openssh-server"))
# sshd -t needs host keys and, on Debian and Ubuntu, /run/sshd. Most
# packages leave both to the service's first start.
exec "ssh-keygen -A", creates: "/etc/ssh/ssh_host_ed25519_key"
directory "/run/sshd", mode: "0755" if platform.family == "debian"

# Replaces the line that sets the option, commented out or not; appends it
# when no line does.
line "PermitRootLogin no",
     path: "/etc/ssh/sshd_config",
     match: /^#?\s*PermitRootLogin\s/,
     verify: "sshd -t -f %{path}"

line "PasswordAuthentication no",
     path: "/etc/ssh/sshd_config",
     match: /^#?\s*PasswordAuthentication\s/,
     verify: "sshd -t -f %{path}"

# Removes the line if it is there. Debian ships it; sshd's default is "no".
line "no X11 forwarding",
     path: "/etc/ssh/sshd_config",
     line: "X11Forwarding yes",
     state: :absent,
     verify: "sshd -t -f %{path}"

Hardening the SSH server's configuration

The plan shows each edit as a diff of sshd_config. With match, the commented-out default line becomes the setting, so the file still reads in the order the package wrote it:

$ sudo sadame plan examples/resources/line
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))

+ directory[/run/sshd]
    state: absent -> present
    mode: 0755

~ line[PermitRootLogin no]
    --- /etc/ssh/sshd_config
    +++ /etc/ssh/sshd_config (desired)
    @@ -30,7 +30,7 @@
     # Authentication:

     #LoginGraceTime 2m
    -#PermitRootLogin prohibit-password
    +PermitRootLogin no
     #StrictModes yes
     #MaxAuthTries 6
     #MaxSessions 10

~ line[PasswordAuthentication no]
    --- /etc/ssh/sshd_config
    +++ /etc/ssh/sshd_config (desired)
    @@ -54,7 +54,7 @@
     #IgnoreRhosts yes

     # To disable tunneled clear text passwords, change to no here!
    -#PasswordAuthentication yes
    +PasswordAuthentication no
     #PermitEmptyPasswords no

     # Change to yes to enable challenge-response passwords (beware issues with

~ line[no X11 forwarding]
    --- /etc/ssh/sshd_config
    +++ /etc/ssh/sshd_config (desired)
    @@ -87,7 +87,6 @@
     #AllowAgentForwarding yes
     #AllowTcpForwarding yes
     #GatewayPorts no
    -X11Forwarding yes
     #X11DisplayOffset 10
     #X11UseLocalhost yes
     #PermitTTY yes

Plan: 1 to create, 3 to update, 0 to remove, 0 to run, 2 unchanged.
$ sudo sadame apply examples/resources/line
sadame 0.5.1 apply on local (Debian GNU/Linux 12 (bookworm))

unchanged  package[openssh-server]
unchanged  exec[ssh-keygen -A]
changed    directory[/run/sshd]
           state: absent -> present
           mode: 0755
changed    line[PermitRootLogin no]
           --- /etc/ssh/sshd_config
           +++ /etc/ssh/sshd_config (desired)
           @@ -30,7 +30,7 @@
            # Authentication:

            #LoginGraceTime 2m
           -#PermitRootLogin prohibit-password
           +PermitRootLogin no
            #StrictModes yes
            #MaxAuthTries 6
            #MaxSessions 10
changed    line[PasswordAuthentication no]
           --- /etc/ssh/sshd_config
           +++ /etc/ssh/sshd_config (desired)
           @@ -54,7 +54,7 @@
            #IgnoreRhosts yes

            # To disable tunneled clear text passwords, change to no here!
           -#PasswordAuthentication yes
           +PasswordAuthentication no
            #PermitEmptyPasswords no

            # Change to yes to enable challenge-response passwords (beware issues with
changed    line[no X11 forwarding]
           --- /etc/ssh/sshd_config
           +++ /etc/ssh/sshd_config (desired)
           @@ -87,7 +87,6 @@
            #AllowAgentForwarding yes
            #AllowTcpForwarding yes
            #GatewayPorts no
           -X11Forwarding yes
            #X11DisplayOffset 10
            #X11UseLocalhost yes
            #PermitTTY yes

Summary
  changed      4  directory[/run/sshd]
                  line[PermitRootLogin no]
                  line[PasswordAuthentication no]
                  line[no X11 forwarding]
  unchanged    2
  failed       0
  skipped      0
  notified     0

Finished in 0.57s.

$ sudo sadame plan examples/resources/line
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))

Plan: 0 to create, 0 to update, 0 to remove, 0 to run, 6 unchanged.

The edits do not restart the SSH server. Add notifies: [:restart, "service[ssh]"] (sshd on Rocky Linux and Arch) when they should take effect right away.

Writing match

match is a regular expression for one line, without its newline. Make it match every form the setting can take in the file, commented out or not, with or without spaces, so that the line replaces the setting instead of being appended after it:

You want match
Port 2222 where the file has #Port 22 /^#?\s*Port\s/
vm.swappiness = 10 where the file may have vm.swappiness=60 /^#?\s*vm\.swappiness\s*=/
include_dir = 'conf.d' in postgresql.conf /^#?\s*include_dir\s*=/

When more than one line matches, every one of them is replaced, so the file ends up with the same line twice rather than two different values.

Common mistakes

  • Managing the same file with file and line. The file resource writes the whole content on every run and line changes it again, so neither ever reports "unchanged". Use one or the other.
  • A match that misses the active setting. If the file has PermitRootLogin yes and match is /^#PermitRootLogin/, nothing matches, so PermitRootLogin no is appended at the end, and sshd keeps the first value it reads: yes. Cover both forms: /^#?\s*PermitRootLogin\s/.
  • Expecting line to create the file. It fails with "does not exist". Create the file with file, or install the package that ships it, earlier in the recipe.

See also

  • file to own a whole file.
  • exec for edits that are not one line.