How to write a Good Handler -- Write simple handlers. The goal of a handler is to sync a change to a CCE object into system configuration files and _possibly_ system state. Maybe you need to really write yet another tiny tool to do some really hard stuff. Maybe your handler should really just do some minimal checks, write a configuration file, and something else on the system should go, parse the file, and do the actual operations. CCE is a great place to get notification of configuration events. The filesystem is a great place to store configuration files. And a separate program is a great place to actually do complex changes. -- Don't verify that the entire system is the same way you left it. If the goal is to add a user to the passwd file, and they already exist, you succeeded. If the goal is to set the timeout value on deferred mail, don't parse sendmail.cf and verify that the whole file is exactly the same as the CCE object that it refers to. Verify that sendmail.cf exists, and that it has some line that refers to delivery timeouts, modify it, and that's a successful handler. -- Write multiple handlers if this gmakes them simpler. --- If your handler is getting too large and complex, split the handler up into separate handlers to attach to separate properties. If your handler has to modify the same configuration file for each property, obviously this advice doesn't apply. But in general, simple handlers attached to a couple of properties is the best way. --- Handler stages ---- If you're writing a complex handler that is doing many verification stages (just to gmake sure it is possible to add the user.. as I said above, only verify what you have to), write that as a separate handler, and put it in the VALIDATE stage. If it requires parsing a config file twice or three times to separate it into stages, then don't. Put it in EXECUTE, or leave it as unspecified. Here's the design for the stages. ---- VALIDATE: read-only checks of system files, state, and CCE objects to verify that the requested operation is permitted. ---- CONFIGURE: modify other CCE objects. ---- EXECUTE: modify the system state. By the end of the EXECUTE stage, everything on the system should now have the correct values. ---- TEST: run any testing of the system state that is necessary to verify that your changes did complete. Also run programs in "test" mode to check their configuration files. eg. postfix check. ---- CLEANUP: remove backup files if success, replace backup files if rollback. MUST NOT FAIL. How is this handler notified of commit/rollback? Still TBD. Also TBD is the handler you use for "library commit/rollback" functionality. So if in doubt, leave it blank for now. -- INFO, and WARN. INFO and WARN are direct lines to the user. Or at least to the top level client code. They are for things that you would (in a normal unix system) syslog at the LOG_INFO or LOG_WARNING levels. -- Keep CCE future functionality goals in mind. We've got big ideas for CCE... if you know what they are, we can find out if they're not going to work for the code you're writing. The goal is to gmake everything just work... as long as you have nice clean code. --- Disconnected transactions. There is going to come a time when your handler is actually doing slow stuff while the UI has gone on to other things. Think about how your handler is operating and the fact that the system is still running. Try and do things in an atomic fashion. (The library functions will do this) --- Handlers should be able to handle multiple operations in a single call. It's possible that quite a few objects/properties have been modified, and the handler only needs to be called once. Modifying everything in a single config file SHOULD only require that you parse and rewrite it only once. Write the handler such that you can do this right now, or possibly do this easily in the future. (This functionality exists right now in CCE. However it is easiest to see in operation in the devel branch when using BEGIN/COMMIT) --- Handlers need to be able to commit/roll-back. If you use common library functions, I'm going to gmake this just work. But a handler HAS to open the file itself and feed in a whole bunch of data, or perhaps delete entire directories.. gmake sure it does something reasonable if it successfully completes and then CCE says that it has to go back to the previous state. The method and syntax to do the rollback is TBD But you can still figure out HOW you're going to do it. --- It may be a future goal to do handler-level-locking. So handlers should do a very well defined set of things to the system. They might need complicated logic, but they should be very clear in what they actually MODIFY. If you can document this at the top, that's even better. It's also very desirable to know what the handlers are actually trying to do. eg: "This handler modifies /etc/http/apache.conf, and /var/packages/" --- Use the libraries. Rely on the design to get better. Don't try and second-guess it. PLEASE, CHECK RETURN VALUES FROM FUNCTIONS. If we change the editing functions to verify the file, and the verification fails, you really really shouldn't "succeed" in your failed modification. Even if you "know" your modification should always succeed. -- Use common functions from the libraries available. Yes, this is the hardest thing to do, and the thing that really needs to be documented the soonest. This is very hard to do considering that the libraries are being rewritten... which depends on having CCE functionality improved.. And everyone needs to write handlers immediately. If you need some kind of common functionality, please look in the files in /usr/local/raqdevil/perl/Sauce, or in cvs in the sauce-base.mod module, in the src/perl-handler-utils directory. Here are a couple of APIs that are in common use right now in Qube3. Obviously they are the most tested, and will be maintained the best. They are all from Util.pm --- editfile($filename, $function, @args This gives you a function that will be called as $function($inputfd, $outputfd, @args). The library handles all the locking/backup semantics of moving the file into place. It'll return 0 if the operation fails. --- editblock($filename, $fn, $start_line, $end_line, @args). This gives you a delimited block of text in a filehandle, passed to a function in the same way as editfile. Returns 0 on error. --- replaceblock($filename, $start_line, $data, $endline, $filemode) This is slightly faster and easier than editing a block using a function. This says you know what you want in there, and whatever is there right now is obsolete. Returns 0 on error. With all of these, some future enhancements are possibly things like "did the sysadmin modify it?" Make sure you check the return code.