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
[email protected]
https://cfengine.org/mailman/listinfo/help-cfengine