0

With our cfengine setup I keep wanting to define classes consisting of groups of hosts, and we have lots of hosts with 4-part hostnames in which the short name is non-unique. I've repeatedly read docs that say not to use dots in hostnames, but it seems like sometimes in certain cases, it works to use xxx_yyy_domain_com, for instance:

in a roles file we define an array with a bunch of machines to be nameservers:

bundle agent tfn_roles {

vars:

# DNS servers
"dns_servers" slist => {
    "hetzner8",     # ns-frk
    "ubiquity1",    # ns-lax
    "ns_ubi3_domain_org",
    "vps001_dfw_domain_org",   # ns-dfw
    "tagadab2",     # ns-lcy
    "atlantic1",    # ns-mco
};

and then in a bind9.cf promise file:

bundle agent service_bind9 {

meta:
    "tags" slist => { "autorun" };

classes:
    "dns_servers" or => { "@(tfn_roles.dns_servers)" };

and a bunch of promises for that class seem to correctly get executed for those 2 hosts that have FQDNs with underscores.

however we have another class that is defined by only a string, in that same roles file:

# Server running the daily tasks - should only be one.
"daily_tasks_server" string => "vps007_dfw_domain_org";

and then in a daily tasks promise file:

bundle agent service_daily_tasks {

meta:
    "tags" slist => { "autorun" };

classes:
    "daily_task_server" expression => "$(tfn_roles.daily_tasks_server)";

And this doesn't seem to work.

Can someone explain why, and a way around it? do i have to say instead:

"daily_task_server" or => "$(tfn_roles.daily_tasks_server)"; 

and if so why? am i misunderstanding some fundamental cfengine syntax rule??

UPDATE: no, making the change above still doesn't work.

(btw I have already read Host group on CFEngine - please don't tell me i have to read Mr. Zamboni's book. Although I'd love to at some point, when i have time...)

Community
  • 1
  • 1
steev
  • 916
  • 10
  • 24
  • There are no dots allowed in a *class name*. You can have all the dots and special characters you want in a variable (vars promise). – Wildcard Feb 15 '16 at 21:05

1 Answers1

1

The error would be in your use of the "expression" attribute (also the "or" attribute) of your classes promises. You're using "$(tfn_roles.daily_tasks_server)", which evaluates to "vps007_dfw_domain_org" in your example. Then CFEngine parses that as a class expression, sees that there is no class set with the name vps007_dfw_domain_org and so evaluates the whole class expression as false. Since the expression attribute of the classes promise evaluates to false ("!any" if you look under the hood), the class "daily_task_server" is not set.

I'm not quite understanding what you're expecting to do with the "daily_task_server" class, to be honest. Classes are booleans, and you seem to be trying to stuff a string into a class. Perhaps you should be using a vars promise? But without seeing what you're ultimately trying to accomplish it's hard for me to be sure.

http://www.cfenginetutorial.org/ has recently come online; you may find it helpful in clearing up CFEngine syntax and definitions of terms. (Full disclosure: I work closely with the author. ;)


EDIT: Based on your comment that you want one value for one specific server and a different (default) value for all other servers, you would want code something like the following:

bundle agent whatever {
  vars:
    any::
      "myvar"
        string => "Default value to apply to all servers";

      "myvar"
        string => "Value only to apply to host001.mydomain.com",
        ifvarclass => strcmp( "$(sys.fqhost)", "host001.mydomain.com" );
}

It doesn't matter what type of promise you are using. I haven't used packages promises very much, but something like the following could be done, again without using classes, just ifvarclass attribute:

bundle agent handle_packages {
  packages:
      "apache"
        policy => "present",
        package_module => "yum",
        version => "2.2.22",
        ifvarclass => strcmp( "$(sys.fqhost)", "host001.mydomain.com" ),
        comment => "Only install this on host001";

      "apache"
        policy => "absent",
        package_module => "yum",
        ifvarclass => not( strcmp( "$(sys.fqhost)", "host001.mydomain.com" )),
        comment => "...Remove it everywhere else.";
}

I haven't used packages promises much as I'm still on 3.6.6 but the ifvarclass attribute can be used in any promise type.

Wildcard
  • 1,302
  • 2
  • 20
  • 42
  • What I was expecting is to have a role (group of servers) just like with my nameserver example, but that only includes one machine. but then we need an "inverse" role that is all the other servers that aren't that one (because some software should only be running on that one). Thanks for the link, looks very useful. Although we recently switched to Rudder so down and dirty cf syntax is less something we deal with directly now.... – steev Feb 15 '16 at 18:44
  • @steev, if it's just one server you want to apply the separate value for, you may as well stick it in an `ifvarclass` attribute (or an `if` attribute, which is the same thing if you're using a newer version of CFEngine—I'm used to 3.6.6.) See edit above. – Wildcard Feb 15 '16 at 20:11
  • I may have not made it clear, or i'm misunderstanding you, @wildcard. In this case i'm not applying variables differently for different servers. I want to apply one promise to one server and another promise to all the others (install a package on the one, remove it from all others). the variable is just to hold the name of the one server. – steev Feb 22 '16 at 22:11
  • 1
    @steev, perhaps you are looking for the [canonify](https://docs.cfengine.com/lts/reference-functions-canonify.html) function? But again, my recommendation is to keep it simple unless you have a lot of promises that are applying to those specific hosts. Edited to show example. – Wildcard Feb 22 '16 at 23:27