From a3968f831306534c32aa3873e64c2e5a7fdfc811 Mon Sep 17 00:00:00 2001
From: Steven Armstrong <steven@icarus.ethz.ch>
Date: Fri, 18 May 2018 01:25:35 +0200
Subject: [PATCH] rewrite __line type for --before and --after support

Signed-off-by: Steven Armstrong <steven@icarus.ethz.ch>
---
 cdist/conf/type/__line/explorer/state         |  72 +++++++--
 cdist/conf/type/__line/gencode-remote         | 148 ++++++++++--------
 cdist/conf/type/__line/man.rst                | 100 +++++++-----
 .../conf/type/__line/parameter/default/state  |   1 +
 cdist/conf/type/__line/parameter/optional     |   6 +-
 5 files changed, 211 insertions(+), 116 deletions(-)
 create mode 100644 cdist/conf/type/__line/parameter/default/state

diff --git a/cdist/conf/type/__line/explorer/state b/cdist/conf/type/__line/explorer/state
index 08056c86..1f81b540 100755
--- a/cdist/conf/type/__line/explorer/state
+++ b/cdist/conf/type/__line/explorer/state
@@ -1,6 +1,6 @@
-#!/bin/sh
+#!/bin/sh -e
 #
-# 2012-2013 Nico Schottelius (nico-cdist at schottelius.org)
+# 2018 Steven Armstrong (steven-cdist at armstrong.cc)
 #
 # This file is part of cdist.
 #
@@ -17,26 +17,64 @@
 # You should have received a copy of the GNU General Public License
 # along with cdist. If not, see <http://www.gnu.org/licenses/>.
 #
-#
 
-file="/$__object_id"
-[ -f "$__object/parameter/file" ] && file=$(cat "$__object/parameter/file")
+if [ -f "$__object/parameter/before" ]; then
+   position="before"
+elif [ -f "$__object/parameter/after" ]; then
+   position="after"
+fi
 
 if [ -f "$__object/parameter/regex" ]; then
-    regex=$(cat "$__object/parameter/regex")
-    greparg=""
+   needle="regex"
 else
-    if [ ! -f "$__object/parameter/line" ]; then
-        echo "Parameter line and regex missing - cannot explore" >&2
-        exit 1
-    fi
-    regex="$(cat "$__object/parameter/line")"
-    greparg="-F -x"
+   needle="line"
 fi
 
-# Allow missing file - thus 2>/dev/null
-if grep -q $greparg -- "$regex" "$file" 2>/dev/null; then
-    echo present
+if [ -f "$__object/parameter/file" ]; then
+   file="$(cat "$__object/parameter/file")"
 else
-    echo absent
+   file="/$__object_id"
 fi
+
+awk -v position="$position" -v needle="$needle" '
+BEGIN {
+   getline anchor < (ENVIRON["__object"] "/parameter/" position)
+   getline pattern < (ENVIRON["__object"] "/parameter/" needle)
+   state = "absent"
+}
+{
+   if (position == "after") {
+      if (match($0, anchor)) {
+         getline
+         if (match($0, pattern)) {
+            state = "present"
+         }
+         else {
+            state = "wrongposition"
+         }
+         exit 0
+      }
+   }
+   else if (position == "before") {
+      if (match($0, pattern)) {
+         getline
+         if (match($0, anchor)) {
+            state = "present"
+         }
+         else {
+            state = "wrongposition"
+         }
+         exit 0
+      }
+   }
+   else {
+      if (match($0, pattern)) {
+         state = "present"
+         exit 0
+      }
+   }
+}
+END {
+   print state
+}
+' "$file"
diff --git a/cdist/conf/type/__line/gencode-remote b/cdist/conf/type/__line/gencode-remote
index 4a75b4c5..7951ea49 100755
--- a/cdist/conf/type/__line/gencode-remote
+++ b/cdist/conf/type/__line/gencode-remote
@@ -1,7 +1,6 @@
 #!/bin/sh -e
 #
-# 2012 Nico Schottelius (nico-cdist at schottelius.org)
-# 2014 Steven Armstrong (steven-cdist at armstrong.cc)
+# 2018 Steven Armstrong (steven-cdist at armstrong.cc)
 #
 # This file is part of cdist.
 #
@@ -18,76 +17,101 @@
 # You should have received a copy of the GNU General Public License
 # along with cdist. If not, see <http://www.gnu.org/licenses/>.
 #
-#
 
-file="/$__object_id"
-regex=""
-state_should="present"
-[ -f "$__object/parameter/file" ]  && file=$(cat "$__object/parameter/file")
-[ -f "$__object/parameter/regex" ] && regex=$(cat "$__object/parameter/regex")
-[ -f "$__object/parameter/state" ] && state_should=$(cat "$__object/parameter/state")
-[ -f "$__object/parameter/line" ]  && line=$(cat "$__object/parameter/line")
+if [ -f "$__object/parameter/before" -a -f "$__object/parameter/after" ]; then
+   echo "Use either --before OR --after but not both." >&2
+   exit 1
+fi
 
+state_should="$(cat "$__object/parameter/state")"
 state_is="$(cat "$__object/explorer/state")"
 
-[ "$state_should" = "$state_is" ] && exit 0
+if [ "$state_should" = "$state_is" ]; then
+   # nothing to do
+   exit 0
+fi
 
+if [ -f "$__object/parameter/before" ]; then
+   position="before"
+elif [ -f "$__object/parameter/after" ]; then
+   position="after"
+else
+   # By default we append to the end of the file.
+   position="end"
+fi
+
+if [ -f "$__object/parameter/regex" ]; then
+   needle="regex"
+else
+   needle="line"
+fi
+
+if [ -f "$__object/parameter/file" ]; then
+   file="$(cat "$__object/parameter/file")"
+else
+   file="/$__object_id"
+fi
+
+add=0
+remove=0
 case "$state_should" in
-    present)
-        if [ ! "$line" ]; then
-            echo "Required parameter \"line\" is missing" >&2
-            exit 1
-        fi
+   present)
+      if [ "$state_is" = "wrongposition" ]; then
+         echo updated >> "$__messages_out"
+         remove=1
+      else
+         echo added >> "$__messages_out"
+      fi
+      add=1
+   ;;
+   absent)
+      echo removed >> "$__messages_out"
+      remove=1
+   ;;
+esac
 
-        #echo "echo \"$line\" >> $file"
-        #line_sanitised=$(cat "$__object/parameter/line" | sed 's/"/\"/g')
-        # Idea: replace ' in the string:
-        # '"'"'
-        # |------> ': end the string
-        #  |-|---> "'": create ' in the output string
-        #     |--> ': continue the string
-        #
-        # Replace all \ so \t and other combinations are not interpreted
-        #
-
-
-        # line_sanitised=$(cat "$__object/parameter/line" | sed -e "s/'/'\"'\"'/g" -e 's/\\/\\\\/g')
-        # The one above does not work:
-        #     --line "PS1='[\t] \[\033[1m\]\h\[\033[0m\]:\w\\$ '"
-        # becomes
-        # PS1='[\\t] \\[\\033[1m\\]\\h\\[\\033[0m\\]:\\w\\$ '
-
-        # Only replace ' with '"'"' and keep \ as they are
-        line_sanitised=$(cat "$__object/parameter/line" | sed -e "s/'/'\"'\"'/g")
-        printf '%s' "printf '%s\n' '$line_sanitised' >> $file"
-        echo "added" >> "$__messages_out"
-
-    ;;
-    absent)
-        if [ "$regex" -a "$line" ]; then
-            echo "Mutally exclusive parameters regex and line given for state absent" >&2
-            exit 1
-        fi
-
-        greparg=""
-        if [ "$line" ]; then
-            regex="$line"
-            greparg="-F -x"
-        fi
-
-        cat << eof
+cat << DONE
 tmpfile=\$(mktemp ${file}.cdist.XXXXXXXXXX)
 # preserve ownership and permissions of existing file
 if [ -f "$file" ]; then
    cp -p "$file" "\$tmpfile"
 fi
-grep -v $greparg "$regex" '$file' > \$tmpfile || true
+
+awk -v position="$position" -v needle="$needle" -v remove=$remove -v add=$add '
+BEGIN {
+   line_file = ENVIRON["__object"] "/parameter/line"
+   getline line < line_file
+   # Need to close line file as it may be re-read as pattern below.
+   close(line_file)
+   getline pattern < (ENVIRON["__object"] "/parameter/" needle)
+   getline anchor < (ENVIRON["__object"] "/parameter/" position)
+}
+{
+   if (remove) {
+      if (match(\$0, pattern)) {
+         # skip over this line -> remove it
+         next
+      }
+   }
+   if (add) {
+      if (anchor && match(\$0, anchor)) {
+         if (position == "before") {
+            print line
+            print
+         } else if (position == "after") {
+            print
+            print line
+         }
+         next
+      }
+   }
+   print
+}
+END {
+   if (add && position == "end") {
+      print line
+   }
+}
+' "$file" > "\$tmpfile"
 mv -f "\$tmpfile" "$file"
-eof
-        echo "removed" >> "$__messages_out"
-    ;;
-    *)
-        echo "Unknown state: $state_should" >&2
-        exit 1
-    ;;
-esac
+DONE
diff --git a/cdist/conf/type/__line/man.rst b/cdist/conf/type/__line/man.rst
index b63ea2b3..d651985e 100644
--- a/cdist/conf/type/__line/man.rst
+++ b/cdist/conf/type/__line/man.rst
@@ -13,72 +13,102 @@ This cdist type allows you to add lines and remove lines from files.
 
 REQUIRED PARAMETERS
 -------------------
+None.
+
 
 OPTIONAL PARAMETERS
 -------------------
-state
-    'present' or 'absent', defaults to 'present'
+after
+    Insert the given line after this pattern.
 
-line
-    Specifies the line which should be absent or present
-
-    Must be present, if state is present.
-    Must not be combined with regex, if state is absent.
-
-regex
-    If state is present, search for this pattern and add
-    given line, if the given regular expression does not match.
-
-    In case of absent, ensure all lines matching the
-    regular expression are absent.
-
-    The regular expression is interpreted by grep.
-
-    Must not be combined with line, if state is absent.
+before
+    Insert the given line before this pattern.
 
 file
     If supplied, use this as the destination file.
     Otherwise the object_id is used.
 
+line
+    Specifies the line which should be absent or present.
+
+    Must be present, if state is 'present'.
+    Ignored if regex is given and state is 'absent'.
+
+regex
+    If state is 'present', search for this pattern and if it matches add
+    the given line.
+
+    If state is 'absent', ensure all lines matching the regular expression
+    are absent.
+
+    The regular expression is interpreted by awk's match function.
+
+state
+    'present' or 'absent', defaults to 'present'
+
+
+
+BOOLEAN PARAMETERS
+------------------
+None.
+
+
 MESSAGES
 --------
 added
-        The line was added.
+    The line was added.
+
+updated
+    The line or its position was changed.
 
 removed
-        The line was removed.
+    The line was removed.
+
 
 EXAMPLES
 --------
 
 .. code-block:: sh
 
-    # Manage the DAEMONS line in rc.conf
-    __line daemons --file /etc/rc.conf --line 'DAEMONS=(hwclock !network sshd crond postfix)'
+    # Manage a hosts entry for www.example.com.
+    __line /etc/hosts \
+        --line '127.0.0.2 www.example.com'
 
-    # Ensure the home mount is present in /etc/fstab - explicitly make it present
-    __line home-fstab \
-        --file /etc/fstab \
-        --line 'filer.fs:/vol/home /home  nfs    defaults        0 0' \
-        --state present
+    # Manage another hosts entry for test.example.com.
+    __line hosts:test.example.com \
+        --file /etc/hosts \
+        --line '127.0.0.3 test.example.com'
 
-    # Removes the line specifiend in "include_www" from the file "lighttpd.conf"
-    __line legacy_timezone --file /etc/rc.conf --regex 'TIMEZONE=.*' --state absent
+    # Remove the line starting with TIMEZONE from the /etc/rc.conf file.
+    __line legacy_timezone \
+       --file /etc/rc.conf \
+       --regex 'TIMEZONE=.*' \
+       --state absent
+
+    # Insert a line before another one.
+    __line password-auth-local:classify \
+        --file /etc/pam.d/password-auth-local \
+        --line '-session required pam_exec.so debug log=/tmp/classify.log /usr/local/libexec/classify' \
+        --before '^session[[:space:]]+include[[:space:]]+password-auth-ac$'
+
+    # Insert a line after another one.
+    __line password-auth-local:classify \
+        --file /etc/pam.d/password-auth-local \
+        --line '-session required pam_exec.so debug log=/tmp/classify.log /usr/local/libexec/classify' \
+        --after '^session[[:space:]]+include[[:space:]]+password-auth-ac$'
 
 
 SEE ALSO
 --------
-:strong:`grep`\ (1)
+:strong:`cdist-type`\ (7)
 
 
 AUTHORS
 -------
-Nico Schottelius <nico-cdist--@--schottelius.org>
+Steven Armstrong <steven-cdist--@--armstrong.cc>
 
 
 COPYING
 -------
-Copyright \(C) 2012-2013 Nico Schottelius. You can redistribute it
-and/or modify it under the terms of the GNU General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
+Copyright \(C) 2018 Steven Armstrong. Free use of this software is
+granted under the terms of the GNU General Public License version 3 (GPLv3).
diff --git a/cdist/conf/type/__line/parameter/default/state b/cdist/conf/type/__line/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__line/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__line/parameter/optional b/cdist/conf/type/__line/parameter/optional
index 604a203e..f89a2115 100644
--- a/cdist/conf/type/__line/parameter/optional
+++ b/cdist/conf/type/__line/parameter/optional
@@ -1,4 +1,6 @@
-state
-regex
+after
+before
 file
 line
+regex
+state