Hi all

I didn't get much consideration with my latest messages, but I will try
again :-D

As said on May 24th, I am trying to manage /etc/hosts in a decent way,
adding records that should be there but aren't, or removing some which
are there but shouldn't. After a lot of trial and error, and some regex
wrestling, I came out with the first policy in attachment which does the
job.

Then I said: OK, let's make it parametric, so that we can specify the
hosts file path and which IP address we consider the main one (instead
of relying on $(sys.ipv4) for example). So I changed a few things here
and there and produced the second policy in attachment.

But in this second policy, readstringarray seems to have stopped working
properly: $(count) gets its value right, but $(records) is not filled
out, so $(ip) doesn't get it's stuff, all the classes I use to determine
what to do don't get defined, and spurious stuff is added to the file.

I inspected the file and tested several times, but I came to nothing. A
"diff -w" of these two files doesn't show any change that should result
in this behaviour.

Am I missing anything obvious here?

Thanks for your help

Ciao
-- bronto

PS: tested with 3.3.1 and 3.3.2
body common control
{
  bundlesequence => { "hostfiles" } ;
  inputs => { "cfengine_stdlib.cf" } ;
  version => "test" ;
}

bundle agent hostfiles
{
  vars:
      "hostfile" string => "/var/cfengine/testfiles/hosts" ;

  files:
    "$(hostfile)"
      edit_line => fix_host_entries ;
}

bundle edit_line fix_host_entries
{
  vars:
      "re_ipv4"          string => escape("$(sys.ipv4)") ;
      "re_local"         string => escape("127.0.1.1") ;
      "re_fqhost"        string => escape("$(sys.fqhost)") ;
      "re_uqhost"        string => escape("$(sys.uqhost)") ;
      "re_domain"        string => escape("$(sys.domain)") ;
      "re_hostaddr"      string => "($(re_local)|$(re_ipv4))" ;
      "host_record"      string => "$(sys.fqhost) $(sys.uqhost)" ;

      "ipv6standard"
          slist => {
                     "::1       localhost",
                     "fe00::0   ip6-localnet",
                     "ff00::0   ip6-mcastprefix",
                     "ff02::1   ip6-allnodes",
                     "ff02::2   ip6-allrouters",
                   } ;

      "count" int => readstringarray("records",      # array to populate
                                     "$(hostfiles.hostfile)",  # file to read
                                     "\s*#[^\n]*?",  # match comments
                                     "\s+",          # match fields
                                     "1000",         # max entries
                                     "80000") ;      # max bytes

      "ip"             slist  => getindices("records") ;
      "ipclass[$(ip)]" string => canonify("$(ip)") ;
      "myaddr_class"   string => canonify("$(sys.ipv4)") ;

  classes:
      "has_ip_$(ipclass[$(ip)])" expression => "any" ;
      "has_ipv4_localhost"       expression => "has_ip_127_0_0_1" ;
      "has_ipv6_localhost"       expression => "has_ip___1" ;
      "has_host_record"          or => { "has_ip_127_0_1_1", 
"has_ip_$(myaddr_class)" } ;

  delete_lines:
    # Thanks to oha for his help with this pattern. I was trying to solve
    # this with a negative lookbehind, (host name *not* preceded by...) and
    # I didn't realise that a positive lookbehind at the ^ (beginning of
    # line NOT followed by...) would work!
    # Anyway, the pattern below means:
    # Beginning of line
    # not followed by either the local address or our ip address, and a space
    # then we start matching the real thing:
    # an address (IPv4 or IPv6; this RE matches much more than that, KISS...)
    # a sequence starting with whitespace followed by an hostname, 0 or more 
times
    # then whitespace and our hostname, either unqualified or qualified
    # then again whitespace and hostname sequence, 0 or more times
    # whitespace padding the end of line, 0 or more
    #
    # This means that this RE matches all the lines which contain our hostname,
    # either qualified or unqualfied, but not associated with a proper IP 
address.
    # It's a dangerous line, and we wipe it.
    hosts_records_normalized::
      
"^(?!($(re_local)|$(re_ipv4))\s)[a-fA-F0-9:\.]+(\s+[a-zA-Z0-9\.\-]+)*\s$(re_uqhost)(\.$(re_domain))?(\s+[a-zA-Z0-9\.\-]+)*\s*"
 ;

  insert_lines:
    hosts_records_normalized::
      "127.0.0.1$(const.t)localhost"
        ifvarclass => "!has_ipv4_localhost";

      "::1$(const.t)localhost ip6-localhost ip6-loopback"
        ifvarclass => "!has_ipv6_localhost" ;
      
      "$(sys.ipv4)$(const.t)$(host_record)"
        ifvarclass => "!has_host_record" ;

  replace_patterns:
    hosts_records_normalized::
      "^127\.0\.0\.1\t(?!localhost\b)(.*)"
        replace_with => value("127.0.0.1$(const.t)localhost $(match.1)"),
        ifvarclass   => "has_ipv4_localhost" ;

      "^::1\t(?!localhost\b)(.*)"
        replace_with => value("::1$(const.t)localhost $(match.1)"),
        ifvarclass   => "has_ipv6_localhost" ;

      "^$(re_hostaddr)\t(?!$(re_fqhost)\s+$(re_uqhost))(.*)"
        replace_with => value("$(match.1)$(const.t)$(host_record)$(match.2)"),
        ifvarclass   => "has_host_record" ;


    !hosts_records_normalized::
      "^(\s*)([a-zA-z0-9:\.]+)( +)"
        replace_with => value("$(match.2)$(const.t)"),
        classes      => if_ok("hosts_records_normalized"),
        comment      => "Normal records begin at column one and are in 
IP\tNAMES format" ;


  reports:
    __debug__::
      "Read $(count) records from $(edit.filename)" ;
      "Address matched: $(ip)" ;

}

body common control
{
  bundlesequence => { "test" } ;
  inputs => { "cfengine_stdlib.cf" } ;
  version => "test" ;
}

bundle agent test
{
  methods:
    "hosts" usebundle => 
hostfile("/var/cfengine/testfiles/hosts","$(sys.ipv4)") ;
}

bundle agent hostfile(hostfile,myip)
{
  files:
    "$(hostfile)"
      edit_line => fix_host_entries("$(myip)") ;

  reports:
}

bundle edit_line fix_host_entries(myip)
{
  vars:
      "re_myip"          string => escape("$(myip)") ;
      "re_local"         string => escape("127.0.1.1") ;
      "re_fqhost"        string => escape("$(sys.fqhost)") ;
      "re_uqhost"        string => escape("$(sys.uqhost)") ;
      "re_domain"        string => escape("$(sys.domain)") ;
      "re_hostaddr"      string => "($(re_local)|$(re_myip))" ;
      "host_record"      string => "$(sys.fqhost) $(sys.uqhost)" ;

      "ipv6standard"
          slist => {
                     "::1       localhost",
                     "fe00::0   ip6-localnet",
                     "ff00::0   ip6-mcastprefix",
                     "ff02::1   ip6-allnodes",
                     "ff02::2   ip6-allrouters",
                   } ;

      "count" int => readstringarray("records",      # array to populate
                                     "$(edit.filename)", # file to read
                                     "\s*#[^\n]*?",  # match comments
                                     "\s+",          # match fields
                                     "1000",         # max entries
                                     "80000") ;      # max bytes

      "ip"             slist  => getindices("records") ;
      "ipclass[$(ip)]" string => canonify("$(ip)") ;
      "myaddr_class"   string => canonify("$(myip)") ;

  classes:
      "has_ip_$(ipclass[$(ip)])" expression => "any" ;
      "has_ipv4_localhost"       expression => "has_ip_127_0_0_1" ;
      "has_ipv6_localhost"       expression => "has_ip___1" ;
      "has_host_record"          or => { "has_ip_127_0_1_1", 
"has_ip_$(myaddr_class)" } ;

  delete_lines:
    # Thanks to oha for his help with this pattern. I was trying to solve
    # this with a negative lookbehind, (host name *not* preceded by...) and
    # I didn't realise that a positive lookbehind at the ^ (beginning of
    # line NOT followed by...) would work!
    # Anyway, the pattern below means:
    # Beginning of line
    # not followed by either the local address or our ip address, and a space
    # then we start matching the real thing:
    # an address (IPv4 or IPv6; this RE matches much more than that, KISS...)
    # a sequence starting with whitespace followed by an hostname, 0 or more 
times
    # then whitespace and our hostname, either unqualified or qualified
    # then again whitespace and hostname sequence, 0 or more times
    # whitespace padding the end of line, 0 or more
    #
    # This means that this RE matches all the lines which contain our hostname,
    # either qualified or unqualfied, but not associated with a proper IP 
address.
    # It's a dangerous line, and we wipe it.
    hosts_records_normalized::
      
"^(?!($(re_local)|$(re_myip))\s)[a-fA-F0-9:\.]+(\s+[a-zA-Z0-9\.\-]+)*\s$(re_uqhost)(\.$(re_domain))?(\s+[a-zA-Z0-9\.\-]+)*\s*"
 ;

  insert_lines:
    hosts_records_normalized::
      "127.0.0.1$(const.t)localhost"
        ifvarclass => "!has_ipv4_localhost";

      "::1$(const.t)localhost ip6-localhost ip6-loopback"
        ifvarclass => "!has_ipv6_localhost" ;
      
      "$(myip)$(const.t)$(host_record)"
        ifvarclass => "!has_host_record" ;

  replace_patterns:
    hosts_records_normalized::
      "^127\.0\.0\.1\t(?!localhost\b)(.*)"
        replace_with => value("127.0.0.1$(const.t)localhost $(match.1)"),
        ifvarclass   => "has_ipv4_localhost" ;

      "^::1\t(?!localhost\b)(.*)"
        replace_with => value("::1$(const.t)localhost $(match.1)"),
        ifvarclass   => "has_ipv6_localhost" ;

      "^$(re_hostaddr)\t(?!$(re_fqhost)\s+$(re_uqhost))(.*)"
        replace_with => value("$(match.1)$(const.t)$(host_record)$(match.2)"),
        ifvarclass   => "has_host_record" ;


    !hosts_records_normalized::
      "^(\s*)([a-zA-z0-9:\.]+)( +)"
        replace_with => value("$(match.2)$(const.t)"),
        classes      => if_ok("hosts_records_normalized"),
        comment      => "Normal records begin at column one and are in 
IP\tNAMES format" ;


  reports:
    __debug__::
      "Read $(count) records from $(edit.filename)" ;
      "Address matched: $(ip)" ;
}
_______________________________________________
Help-cfengine mailing list
Help-cfengine@cfengine.org
https://cfengine.org/mailman/listinfo/help-cfengine

Reply via email to