Working with services: Regular expression error "range out of order in character class"

1,207 views
Skip to first unread message

Steve Ovens

unread,
Mar 7, 2013, 1:37:30 PM3/7/13
to help-c...@googlegroups.com


Hi guys,

while experimenting with turning services on and off, I have run into an interesting situation

According to the docs and the Learning CFEngine 3 book, I should just be able to do something simple like:

bundle agent test
{
        services:
        "ssh"
        service_policy => "stop";
}


This works, however when I try to disable avahi I get a stream of


Regular expression error "range out of order in character class" in expression "$(pattern[avahi-daemon])" at 16

Like so:

body common control
{
           bundlesequence => { "test" };
           inputs => { "/var/cfengine/inputs/libraries/cfengine_stdlib.cf" };
}
bundle agent test
{
        services:
        "avahi-daemon"
        service_policy => "stop";
}

If I change "avahi-daemon" to avahi, I get no output but the service does not shut down. I know that I could always issue the commands to the box but as I understand this is not considered best practice. What am I missing? I have tried several "non-standard" services with the same result as avahi, either no output or a torrent of errors.

I appreciate any pointers that could be offered

Diego Zamboni

unread,
Mar 7, 2013, 1:50:19 PM3/7/13
to Steve Ovens, Diego Zamboni, help-c...@googlegroups.com
Hi,

The service-specific commands, process regular expressions, etc., are defined in the standard_services bundle in cfengine_stdlib.cf.

The confusing error message is because it's trying to expand $(pattern[avahi-daemon]) inside a regular expression, but the string remains unexpanded because that array element doesn't exist in standard_services. Then the regex tries to interpret [avahi-daemon] as a character class (as in [a-z]), but fails because "i-d" represents an invalid range (i is *after* d in ASCII, hence the "range out of order" message). It doesn't fail with [avahi] because that's a valid character class, although of course it doesn't match anything, hence nothing happens.

Phew.

What should be done is to add "avahi", "avahi-daemon" and others in the supported services list in standard_services. Pull requests gladly accepted :)

--Diego


--
You received this message because you are subscribed to the Google Groups "help-cfengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to help-cfengin...@googlegroups.com.
To post to this group, send email to help-c...@googlegroups.com.
Visit this group at http://groups.google.com/group/help-cfengine?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Steve Ovens

unread,
Mar 7, 2013, 2:20:53 PM3/7/13
to help-c...@googlegroups.com, Steve Ovens, Diego Zamboni
Thanks for the explanation Diego!

I will definitely consider a pull request once I have inputted all of the services and ensure that they are working. (I am assuming you mean a pull request on the stdlib and not cfegnine itself :) )

You guys are quite helpful!

Diego Zamboni

unread,
Mar 7, 2013, 2:22:57 PM3/7/13
to Steve Ovens, Diego Zamboni, help-c...@googlegroups.com

I will definitely consider a pull request once I have inputted all of the services and ensure that they are working. (I am assuming you mean a pull request on the stdlib and not cfegnine itself :) )

Yes, a pull request against the stdlib including all the new services that you define, will help us grow the base of supported services.

You guys are quite helpful!

Thanks for the kind words. Happy to help :)

--Diego

Steve Ovens

unread,
Mar 7, 2013, 7:37:57 PM3/7/13
to help-c...@googlegroups.com
Thought I would post a follow up,

Its not a pull request but here is a python script which takes a file produced by either chkconfig or sysv-rc-conf as input and will append any missing services to your stdlib.cf file


#!/usr/bin/python
#This script will attempt to add services to your cfengine stdlib.cf

import os
import sys
import platform
import fileinput

start_line = False
#Attempt to figure out which version of linux you are running
linux_version = platform.platform().lower()
redhat_types = "centos", "fedora", "redhat"
debian_types = "linuxmint", "ubuntu", "debian"
stdlib_file = "/var/cfengine/masterfiles/libraries/cfengine_stdlib.cf"
try:
    chkconfig_file = open(sys.argv[1]).readlines()
except:
    print "I need at least 1 input file to work"
    print "USAGE: add_to_stdlib.py <chkconfig list> <(optional) location of cfengine_stdlib.cf>"
    sys.exit()
   
#I need both a readline and the plain text file because I am going to do an inplace substitution
try:
    stdlib_file = sys.argv[2]
except:
    pass
stdlib = open(stdlib_file).readlines()

if any(similar_os in linux_version for similar_os in redhat_types):
    insert_after = "redhat::"
elif any(similar_os in linux_version for similar_os in debian_types):
    insert_after = "debian|ubuntu::"

#Create a list which will hold all of the services from chkconfig
services_list = []
for line in chkconfig_file:
        #If a line is blank, skip it
        if line in ['\n', '\r\n']:
            pass
        else:
            #Split the line so that we only have the service name
            line = line.split()[0]
            services_list.append(line)

#I am going to create a duplicate list      
sanitized_services = services_list[:]
#Service list is the original list, and we are going to use it to pop entries out of the sanitized list
for new_service in services_list:
    for stdlib_service in stdlib:
        if new_service in stdlib_service:
            if new_service in sanitized_services:
                #If the service already exists in the file, pop it from the sanitized list
                sanitized_services.remove(new_service)
sanitized_services.reverse()
#This does the inplace line additions to the file
for service in sanitized_services:
    for line in fileinput.input(stdlib_file, inplace=1):
            if insert_after in line:
                start_line = True
            else:
                if start_line:
                    print '"startcommand[%s]" string => "/etc/init.d/%s start";' % (service,service)
                    print '"stopcommand[%s]" string => "/etc/init.d/%s stop";' % (service,service)
                    print '"pattern[%s]" string => ".*%s.*";' % (service,service)
                    print ""
                start_line = False
            print line,

Diego Zamboni

unread,
Mar 7, 2013, 7:39:38 PM3/7/13
to Steve Ovens, Diego Zamboni, help-c...@googlegroups.com
Very nice - maybe you want to contribute it to the "tools" section of the design center?

--Diego


Brian Bennett

unread,
Mar 7, 2013, 7:57:43 PM3/7/13
to help-c...@googlegroups.com
Very cool, I like it.

Can you make the destination file configurable? My masterfiles directory is in a non-standard location. It would also help for preparing pull requests, since my repository isn't in /var/cfengine either.


-- 
Brian

Steve Ovens

unread,
Mar 7, 2013, 9:14:37 PM3/7/13
to help-c...@googlegroups.com
print "USAGE: add_to_stdlib.py <chkconfig list> <(optional) location of cfengine_stdlib.cf>"

This piece of code makes it configurable. I already thought of it before I "committed" the changes :)

If you ran the script with no input it would spit this out at you. Basically, if you dont specify the optional location of the stdlib, it will assume the default.

So using it like so

add_to_stdlib.py ./myfile /my/cfengine_loc/inputs/libraries/cfengine_stdlib.cf

It will modify the non-standard location



On Thursday, 7 March 2013 13:37:30 UTC-5, Steve Ovens wrote:
Message has been deleted

Steve Ovens

unread,
Mar 8, 2013, 9:35:29 AM3/8/13
to help-c...@googlegroups.com
Squashed a bug, realized I posted the wrong revision, couldnt figure out how to edit my post. Here is the fixed code

#!/usr/bin/python
#This script will attempt to add services to your cfengine stdlib.cf

import os
import sys
import platform
import fileinput

bundle_start = False

start_line = False
#Attempt to figure out which version of linux you are running
linux_version = platform.platform().lower()
redhat_types = "centos", "fedora", "redhat"
debian_types = "linuxmint", "ubuntu", "debian"
stdlib_file = "/var/cfengine/masterfiles/libraries/cfengine_stdlib.cf"
try:
    chkconfig_file = open(sys.argv[1]).readlines()
except:
    print "I need at least 1 input file to work"
    print "USAGE: add_to_stdlib.py <chkconfig list> <(optional) location of cfengine_stdlib.cf>"
            if "standard_services" in line:
                bundle_start = True

            if insert_after in line:
                start_line = True
            else:
                if start_line and bundle_start:

                    print '"startcommand[%s]" string => "/etc/init.d/%s start";' % (service,service)
                    print '"stopcommand[%s]" string => "/etc/init.d/%s stop";' % (service,service)
                    print '"pattern[%s]" string => ".*%s.*";' % (service,service)
                    print ""
                start_line = False
            if "# METHODS that implement these ....." in line:
                 bundle_start = False
            print line,

Reply all
Reply to author
Forward
0 new messages