eBox developers guide

Javier Uruen

Ricardo Muñoz

Jorge Arcas

Isaac Clerencia

Guillermo Ontañón

Javier Amor

Enrique J. Hernández

This work is licensed under the Creative Commons Attribution License. To view a copy of this license, visit http://creativecommons.org/licenses/by/2.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.


Table of Contents

1. Introduction
1.1. Intended audience
1.2. What is eBox?
1.3. Terminology
1.4. How a module works
1.5. A Model-View-Controller module
1.5.1. Model creation
1.5.1.1. User input management
1.5.2. Model view
1.5.3. Data model publishing and access
1.5.4. When is this approach suitable?
2. Basic API
2.1. EBox::Global
2.2. Exceptions
2.3. Data validation
2.4. i18n
2.4.1. i18n developers
2.4.2. i18n for translators
3. Module backend
3.1. The base module
3.1.1. Module constructor
3.2. Root commands
3.3. EBox::GConfModule
3.4. Ordering stuff
3.5. Controlling a daemon
3.5.1. Configuration file generation
3.5.2. Controlling execution
3.6. gconf schemas
3.6.1. Minimal gconf schema
3.6.2. Default values
3.6.3. Module dependencies
3.7. Backups
3.8. Administrative logging
3.9. Migration scripts
4. Interacting with other components
4.1. Introduction
4.1.1. Observers
4.2. Objects module
4.3. Network module
4.4. Firewall module
4.5. Defining new observers
5. Web frontend
5.1. CGIs
5.2. Mason templates
5.2.1. Standard GUI elements
5.3. The menu
5.4. Summary
6. LDAP based modules
6.1. Introduction
6.2. Fetching the configuration parameters
6.3. Access to the LDAP server
6.4. Implementing an LDAP based module
6.5. Adding functionality to the user interface
6.6. Including your own LDAP schemas
6.7. Adding your own access control list
7. Model-View-Controller internals
7.1. Model
7.1.1. Model relationship
7.1.2. Developing eBox types
7.2. View
7.3. Controller
7.4. Publishing models and CGI mapping
8. How to create a module
8.1. Long way to create a small module
8.1.1. Studying the NTP service and its features
8.1.2. Create a new module from the module template.
8.1.3. Defining and implementing the API
8.1.4. Creating CGIs and templates
8.1.5. Showing the menu and the Summary page
8.1.6. Generating config files and managing the NTP server
8.1.7. Setting up proper firewall rules
8.1.8. Conclusion
8.2. Short way to create a small module
8.2.1. Define data model
8.2.2. Define module web GUI using Composites
8.2.3. Publishing model and composite from this module
8.2.4. Common tasks
8.2.4.1. Configuring Apache2 service
8.2.4.2. Manage Apache2 daemon execution
8.2.4.3. Web service inclusion in eBox menu and summary
8.2.4.4. Establish rules through services and firewall modules to use Web service in eBox
8.2.5. Expose module API
8.2.6. Conclusion
9. Event architecture
9.1. Event watchers
9.2. Event dispatchers

List of Figures

7.1. Data model class diagram
7.2. eBox types class diagram
7.3. View template system class diagram

List of Examples

1.1. Object model definition
1.2. Object member model definition
1.3. Jabber dispatcherconfiguration form data model definition
1.4. A sample model composite definition
1.5. Model and composite publishing example
1.6. Rows dumped result
2.1. Creating a read-only module instance
2.2. Functions for instantiating more than one module
2.3. Throwing a generic internal exception
2.4. Catching an exception
2.5. Using data validation functions
2.6. Using data validation functions with automatic error handling
2.7. Using __n function
3.1. Simple module constructor
3.2. Using EBox::Sudo::root()
3.3. Setting a string key
3.4. Setting a string list
3.5. Getting and using a unique identifier
3.6. Ordering firewall rules
3.7. Generating a configuration file
3.8. Sample _regenConfig implementation
3.9. Sample _stopService implementation
3.10. Minimal gconf schema
3.11. Creating a module instance
3.12. Setting a default value in gconf schemas
3.13. Setting module dependencies
3.14. Overriding backup functions
3.15. Overriding functions for extended backups
3.16. Including LogAdmin package
3.17. Creating an object with administrative logging support
3.18. Adding actions to log
3.19. Calling logAdminDeferred method
4.1. EBox::ObjectsObserver subclass implementation
4.2. Implementing a EBox::NetworkObserver
4.3. Creating custom firewall rules
4.4. Defining an observer that may be implemented by other modules
4.5. Calling observer modules
5.1. Hello world CGI
5.2. Setting the mason template for a CGI
5.3. Passing arguments to a mason template
5.4. Setting the 'msg' attribute
5.5. Using parameters from the HTTP request
5.6. Redirecting a request to a different CGI
5.7. Setting the gettext domain in a CGI
5.8. Declaring arguments in a mason template
5.9. init section in mason templates
5.10. Embedding perl code in a mason template
5.11. Printing strings in a mason template
5.12. HTML snippet for displaying messages
5.13. A table to display data
5.14. Data entry form
5.15. Adding folders and items to the menu
5.16. summary sample implementation
5.17. statusSummary sample implementation
6.1. _groupAddOns implementation
6.2. Implementing _includeLDAPSchemas
7.1. GConf structure from a model
7.2. Component call main
8.1. EBox::NTP constructor
8.2. Enabling the NTP server
8.3. Reading the state of the NTP server
8.4. Enabling the external NTP synchronisation
8.5. Fetching the configuration for external synchronization
8.6. Setting the external NTP servers
8.7. Getting the list of external NTP servers
8.8. Setting a new system time and date
8.9. Setting a new time zone
8.10. Restarting eBox modules and system services
8.11. Constructor for EBox::CGI::NTP::Index
8.12. Feeding the configuration of the NTP server to the mason template
8.13. CGI to enable and disable the NTP server
8.14. Mason template for enabling the NTP server
8.15. datetime.mas template
8.16. Adding entries to the eBox menu
8.17. statusSummary in EBox::NTP
8.18. Generating the /etc/ntp.conf configuration file
8.19. Template to generate /etc/ntp.conf
8.20. _regenConfig method
8.21. NTP daemon management method
8.22. tools/runit/ntpd file
8.23. _stopService method.
8.24. Telling whether the NTP daemon is running or not.
8.25. Firewall configuration
8.26. General configuration settings description
8.27. Virtual host data model description
8.28. Adding name resolution to the virtual host
8.29. General composite to display Web service configuration page
8.30. Publishing models and composites from web server module
8.31. WebServer module constructor
8.32. _regenConfig for web service
8.33. Generating web server configuration files
8.34. Setting up the web server
8.35. ports.conf.mas template
8.36. vhost.mas template
8.37. Web daemon management method
8.38. tools/runit/apache2 file
8.39. tools/runit/apache2.finish file
8.40. Stop web service with _stopService method
8.41. Adding WebServer to the menu
8.42. onInstall and onRemove methods to configure web service
8.43. Debian scripts
8.44. virtualHosts method
8.45. _exposedMethods method
8.46. _exposedMethods usage by a Perl script
9.1. EBox::Event::Watcher::DiskFreeSpace constructor

Chapter 1. Introduction

1.1. Intended audience

This guide is written for developers interested in writing new modules with new features for eBox, and for those who need to change or extend the base eBox framework.

eBox is written in perl and is mostly object-oriented. It is assumed that readers know how to write object oriented programs in perl. Some features of the language or the libraries used in eBox may be discussed, but no attempt has been made to document those aspects throughly.

Some experience writing object oriented applications and using design patterns in any language should be useful, but you should be fine just with the basic concepts of object oriented programming.

An effort has been made to provide examples for all aspects of eBox development explained in this guide. Most of them come straight from existing and working modules. Furthermore, a complete module is developed step by step in Chapter 8. It's a real module so that chapter should cover all the parts of a complete and working module.

1.2. What is eBox?

eBox is a platform for the development and deployment of security and work-group related services on a local network. It is configured through a web interface that integrates all services in a consistent and easy to use way. Its goal is to be usable by non-experts.

eBox is meant to be installed on a dedicated machine, all configuration tasks are performed through the eBox web interface. This means that the configuration of the underlying services is one-way: eBox modules generate configuration files, overwriting system files in some cases (although that tends to be avoided if possible) and manual changes to those files are not detected by eBox. This simplifies the implementation and usage of the package but has the disadvantage that developers need to be careful if they use their own system for testing purposes.

eBox design is modular, new modules providing new services and features can be developed independently from its core package. eBox simplifies the deployment of new modules and the updates of existing ones with a software management module, which is also independent from the eBox base package.

The system is based on Linux and has been developed on top of Debian, no support is provided for other Linux distributions as there are some "debianisms" in some of the modules. Porting to other Linux distributions should be quite easy, and porting to other Unix like operating systems such as OpenBSD would take a little more work but it should still be doable, and worth it.

eBox is based on a few software packages, which are used for several purposes:

Linux 2.6

eBox relies on some features provided by the kernel, some are available only on the 2.6 series. Among these features are netfilter (for the firewall), 802.1q (for VLANs) and ipsec.

perl and mod_perl

All eBox modules are implemented in perl, the web interface runs under mod_perl for performance reasons.

mason

mason is a templating system for perl, it is used to generate HTML for the web based user interface and to generate configuration files.

apache

The web interface is served by apache, we usually use the apache-perl Debian package.

gconf

eBox stores its configuration using gconf2 and its perl library bindings.

sudo

Apache runs as an unprivileged user, all eBox modules use sudo to execute commands that need to be run as root.

The base eBox package provides a development framework for new modules. By using this framework modules automatically get features like configuration backups and reversion of changes in the configuration before they are saved. The features available to module developers will be explained in detail in later chapters.

1.3. Terminology

A few conventions are used throughout this guide regarding several concepts:

Booleans. Whenever we need to talk about a boolean value passed to a method or returned by it, we will refer to a true value as true and to a false value as undef or false.

Perl modules and eBox modules. There may be some confusion in some parts of the guide about the term module as the term may refer to an eBox module or a perl module. An eBox module is the complete set of classes (and perl modules) and other files that provide a self-contained functionality for eBox and that may be installed or uninstalled independently from the rest of eBox. A perl module is basically a perl source file.

Whenever there is no possibility of confusion, we'll just write module, otherwise we'll write explicitly perl module since this meaning will be the one used less frequently.

1.4. How a module works

The typical eBox module handles the configuration of a daemon, possibly integrated with other eBox modules. The developer decides in what ways should the user be able to configure the daemon, this ways do not necessarily map directly to the daemon configuration options on a one-to-one relationship. The developer may pick a sane default value for most of the options and hide them from the user, showing him just the ones that he feels are important. Even further, an option changed by the user through the web interface may cause configuration changes in several real configuration options or even on several eBox modules. The main goal is to have an user interface as simple, easy to use, and integrated as possible, while providing the user with a rich set of features.

However, there may be modules that do not handle the configuration of a network service. An example is the 'sysinfo' module in the base system, it just gathers system information to be shown in the Summary page and provides a few menu entries for features that do not belong to any module in particular. The module parent class defines several abstract methods that real modules are free to leave unimplemented. Thus, a module may just provide info in the Summary, add new menu items, handle a network service or all of the above.

The normal, and most interesting, case is the module described in the first paragraph. Such a module has three parts.

  • It defines and implements an API that will let the GUI, other modules or plain perl scripts configure the daemon it is going to handle.

  • The second is the GUI, which is a set of CGIs that show the current configuration to the user and let him change it, these CGIs use the API defined earlier to fetch the configuration info and make changes to it.

  • The third part of the module is usually quite small, it translates all the configuration information stored in GConf into firewall rules, configuration files and commands that make the network service behave as the user expects. It also takes care of starting, stopping and restarting the service when needed.

This separation between the GUI and the backend opens the possibility for other means of changing the configuration. One such means is through perl scripts, these is useful when making packages for a distribution, the package maintainer can write a simple script to import the current system configuration into eBox, or set up some default values. Another use of the API is for other modules, the firewall module is the most used case, almost all modules need tell the firewall to open some port for them. In the future a wrapper may be written around these APIs to publish them through web-services, this would make eBox configurable programmatically over the network.

Besides these three parts, the module has some other minor parts, like its piece of the summary page in the web interface, menu entries, dependency declarations, backups of configuration pieces not stored in GConf, etc.

That's all there is to it, creating a module is as simple as following these steps:

  • Decide what daemon your module is going to handle and learn how it works and how to set it up.

  • Plan what options you are going to expose to the user through the web interface, and how they may interact with other eBox modules

  • Define and implement the API that will let the GUI manipulate the necessary configuration options. The main class in your module should inherit from EBox::GConfModule, this class wraps the GConf API and transparently implements some useful features that all eBox modules need to have.

  • Create the CGIs and HTML templates that will let the user interact with the module. CGIs should inherit from the EBox::CGI::Base class which, again, provides some features transparently to all its children.

  • Write the code needed to make the daemon work, possibly generating a configuration file and setting up a firewall rule using the EBox::Firewall class. Configuration files are generated almost trivially with mason, which is the template system also used to generate the HTML pages for the GUI.

The size and complexity of a module depends directly on the complexity of the service involved and the amount of configuration items exposed to the user. The necessary work to make a small eBox module is minimal, take the DNSCache module as an example, its CGIs add up to 49 lines of code and the module itself is 134 lines long.

The directory structure of an eBox module may look quite complex for a newcomer. A usual eBox module directory should look like this:

AUTHORS     configure.ac  INSTALL     Makefile.am  README   stubs/
autogen.sh  COPYING       NEWS        schemas/     tools/   ChangeLog
debian/     m4/           po/         src/         www/     migration/
		

The more important directories are src/, schemas/, www/, stubs/ and migration/.

The src/ directory contains the source code for the module. Inside this directory two directories can be found: EBox and templates. The EBox directory contains the Perl source code files, including a CGI subdirectory with the web frontend for the module. The templates directory is used to store the Mason templates, which will be used to generate the HTML output.

The schemas/ directory contains gconf schemas, used to define the configuration schemas for modules, and optionally provide default values for some options.

The www/ directory contains images and stylesheets that will be used in the web frontend.

The stubs/ is used to store Mason templates to generate configuration files for the module services.

The migration/ stores the migration perl scripts to upgrade from an eBox module version to a newer one.

1.5. A Model-View-Controller module

Bear Section 1.4 in mind, a new way to develop eBox modules have been developed. It relies on Model-View-Controller design pattern, which an architectural pattern used in large software projects to decouple highly data (model) and user interface (view) so that developer can change the data architecture wihout changing the user interface and vice versa. A new component is added, the controller, which manages data access and business logic acting as an intermediate between them.

Thus with our new architecture the eBox MVC conception will be the following:

Model

Our model is defined as a perl module which is a subclass of EBox::Model::DataTable or one of the subclasses.

View

The already defined Mason templates are used to show the information to the user.

Controller

The CGIs which are mapped from the Mason templates to do actions. There are some default actions already implemented.

In Chapter 7, there is a large explanation about how it works internally. Below we are only going to introduce the main components which an eBox developer requires to know to build up an eBox MVC's module.

A basic MVC based module should have, at least, these three main parts:

  • A series of data models which represent the information you want to store to configure your desired service.

  • Manage your service daemon (if any) accessing to the data model, add menu entry, summary and make the data contained in the model(s) available to the eBox system.

  • Expose the desired API to other eBox modules, perl scripts and SOAP interface.

1.5.1. Model creation

A model is just a table concept, that is, an array of rows which have a defined fixed definition, the table description. This table description contains a series of EBox::Types which are instances of EBox::Types::Abstract, these eBox types try to include the required representation for eBox configuration parameters, a brief list could be:

Basic types

Basic types include are: Boolean, Int, Text, Password, Select (an enumeration) or Link, which describe a type that stores a simple link reference.

Network related types

These comprise IPAddr which represents an IP address, MACAddr which is a Ethernet address, Service which matches with port and protocol such as /etc/services does and PortRange which describes a single port number, a port range or any port.

Composite types

Currently, just a Union type has been developed which represents a type is used from a defined list of options. If none of options are suitable, a Union::Text may be used with this purpose.

The data storage is managed automatically by the model itself. A model stores its information under a GConf directory which must set explicitly at the model creation. However, you can set dynamically which becomes the model instance in a submodel. The submodels are model instances whose GConf directory is set dynamically, not only at the runtime. Its usage is intimately with EBox::Types::HasMany, which describes a relationship between a field from a model with another model indicating that field contains the whole data from another model.

Apart from the table description, it is required to set several attributes such as the name, the printable name, if it is ordered, etc... Some model definition examples may help to understand the subject:

Example 1.1. Object model definition

sub _table
{
    my @tableHead = 
        ( 

            new EBox::Types::Text
                            (
                                'fieldName' => 'name',
                                'printableName' => __('Name'),
                                'size' => '12',
                                'unique' => 1,
                                'editable' => 1
                             ),
            new EBox::Types::HasMany
                            (
                                'fieldName' => 'members',
                                'printableName' => __('Members'),
                                'foreignModel' => 'MemberTable',
                                'view' => '/ebox/Object/View/MemberTable',
                                'backView' => '/ebox/Object/View/MemberTable',
                                'size' => '1',
                             )

          );

    my $dataTable = 
        { 
            'tableName' => 'ObjectTable',
            'printableTableName' => __('Objects'),
            'automaticRemove' => 1,
            'modelDomain' => 'Objects',
            'defaultActions' => ['add', 'del', 'editField' ],
            'tableDescription' => \@tableHead,
            'class' => 'dataTable',
            'help' => __('Objects'),
            'printableRowName' => __('object'),
        };

    return $dataTable;
}
        

As it is shown, a model is used to describe an eBox object, which has a name and a group of members which are described in Example 1.2:

Example 1.2. Object member model definition

sub _table
{
    my @tableHead = 
        ( 

            new EBox::Types::Text
                            (
                                'fieldName' => 'name',
                                'printableName' => __('Name'),
                                'size' => '12',
                                'unique' => 1,
                                'editable' => 1
                             ),
            new EBox::Types::IPAddr
                            (
                                'fieldName' => 'ipaddr',
                                'printableName' => __('IP Address'),
                                'editable'      => 1,
                            ),
            new EBox::Types::MACAddr
                            (
                                'fieldName' => 'macaddr',
                                'printableName' => __('MAC Address'),
                                'editable'      => 1,
                                'optional' => 1
                            ),


          );

    my $dataTable = 
        { 
            'tableName' => 'MemberTable',
            'printableTableName' => __('Members'),
            'automaticRemove' => 1,
            'defaultActions' => ['add', 'del', 'editField' ],
            'modelDomain'    => 'Objects',
            'tableDescription' => \@tableHead,
            'class' => 'dataTable',
            'help' => __('Objects'),
            'printableRowName' => __('member'),
        };

    return $dataTable;
}
        

As you may see, every field has some attributes as well as the model itself. Just note the actions are the allowed actions to apply to that model. For instance, you can add, delete rows, edit fields... Several attributes are allowed for every type and model, if they are not explicitly set at the model definition, they are treated as false value. That is, if it is a boolean type or empty string if the type is an string.

The best part of it comes from forms. Typical eBox scenarios contain a set of tables and forms. So the information that a form may show can be stored in a data model as described above. However, it has some significant differences. A form data model looks like a table data model with just one row which is always shown to be edited. A form model definition is similar to a table data model one as Example 1.3 depicts:

Example 1.3. Jabber dispatcherconfiguration form data model definition

sub _table
  {

      my @tableDesc =
        (
         new EBox::Types::Text(
                               fieldName     => 'server',
                               printableName => __('Jabber server name'),
                               size          => 12,
                               editable      => 1,
                              ),
         new EBox::Types::Int(
                              fieldName     => 'port',
                              printableName => __('Port'),
                              size          => 6,
                              editable      => 1,
                              defaultValue  => 5222,
                             ),
         new EBox::Types::Text(
                               fieldName     => 'user',
                               printableName => __('Jabber user name'),
                               size          => 12,
                               editable      => 1,
                              ),
         new EBox::Types::Password(
                                   fieldName     => 'password',
                                   printableName => __('User password'),
                                   size          => 12,
                                   editable      => 1,
                                   minLength     => 4,
                                   maxLength     => 25,
                                  ),
         new EBox::Types::Boolean(
                                  fieldName      => 'subscribe',
                                  printableName  => __('Subscribe'),
                                  editable       => 1,
                                 ),
         new EBox::Types::Text(
                               fieldName     => 'adminJID',
                               printableName => __('Administrator Jabber Identifier'),
                               size          => 12,
                               editable      => 1,
                              ),
        );

      my $dataForm = {
                      tableName          => 'JabberDispatcherForm',
                      printableTableName => __('Configure Jabber dispatcher'),
                      modelDomain        => 'Events',
                      tableDescription   => \@tableDesc,
                      class              => 'dataForm',
                      help               => __('In order to configure the Jabber event dispatcher ' .
                                               'is required to be registered at the chosen Jabber ' .
                                               'server or check subscribe to do register. The administrator ' .
                                               'identifier should follow the pattern: user@domain[/resource]'),
                     };

      return $dataForm;

  }
          

As you may notice, fields at a form can contain default values. This is done due to have default eBox configuration working. For example, you may want to enable a service to a specific port whose value does not modify the service if the default value is set.

1.5.1.1. User input management

Once the definition is set you may want to validate the user input. Types do some validation, for instance, IPAddr checks if the parameters contain a valid IP. However, there are cases where the developer requires to do extra validation. To achieve so, there are two methods to override validateRow and validateTypedRow.

Furthermore, actions have callbacks to called after an action have been finished. Therefore add action has addedRowNotify, edit updatedRowNotify and so on. This allows you to manage the updated information to do something different from just storing updating information at GConf database.

1.5.2. Model view

This section explains how the data model is shown at eBox user interface. If you just want to show a data model in eBox, a new menu entry is required. Its URL must have this pattern: ${modelDomain}/View/${tableName}. The modelDomain and tableName are attributes which have been already set at the data model definition. A table model will show up an empty table and a HTML form will appear if a form model is defined, they are named as their viewers.

You may wish to combine the several models in the same page. Here where the model composites play its role. The model composites are containers which stores two kinds of components: models and composites, thus you can create complex composites to find the more usable and adapted user interface for your needs. The current available layouts are:

top-bottom

The components are shown in order from top to the bottom.

tabbed

The component viewers are displayed in a tabbed way. That is, each component is shown when the tab name is clicked

Define a model composite is even simpler than a model one as it depicts in this example:

Example 1.4. A sample model composite definition

sub _description
  {

      my $description =
        {
         components      => [
                             'EnableForm',
                             'ConfigurationComposite',
                            ],
         layout          => 'top-bottom',
         name            => 'GeneralComposite',
         printableName   => __('Events'),
         compositeDomain => 'Events',
         help            => __('Events module may help you to make eBox ' .
                               'inform you about events that happen at eBox ' .
                               'in some different ways'),
        };

      return $description;

  }
        

The components are referenced using its name, tableName at models and name at composites. In order to display a composite based page, it is required to change the menu entry. In this case, the URL to call must follow this pattern ${compositeDomain}/Composite/${name}. That's all you need to display your models together in a single page.

1.5.3. Data model publishing and access

Just get the magic done, it is required to publish the models and composites. To achieve so, the eBox modules, which are subclasses of EBox::GConfModule, must inherit from EBox::Model::ModelProvider and EBox::Model::CompositeProvider interfaces, implementing its respectively methods models and composites, which returns an array reference of model and composite instances that lives within this eBox module realm. An example could be the next one:

Example 1.5. Model and composite publishing example

sub models {
       my ($self) = @_;

       return [
               $self->configureLogModel(),
               $self->forcePurgeModel(),
              ];
}

sub composites
  {

      my ($self) = @_;

      return [
              $self->_configureLogComposite(),
             ];

  }
        

Once the models are published and ready to be displayed, they actually have to do something to manage its service. Current data model API is quite experimental in the way it is constantly evolving to satisfy the client needs. The main components that use data model are migration scripts and eBox module itself to obtain the required data to configure the service.

The main method to retrieve the data model is rows which returns an array reference which contains the rows from that data model. You can filter the result using a filter string or get the result from a defined page, since data model allows filtering and paging. Each array component contains the return value from row which is a hash reference containing the following key - value pairs:

id

Each row has an identifier which allows distinguishing from the others inside the data model. You can use it as well as indexer calling the row method.

order

A data model can be ordered or not. If so, the rows are ordered in the same order they have been added. Furthermore, you might tailor the order overriding _tailoredOrder to give rows in the desired order.

values

Array reference which contains eBox types, which were explained previously.

valueHash

Hash reference indexed by field name whose values are eBox types as values does.

plainValueHash

For convenience, some methods have been added to help accessing the methods. This one returns a hash reference containing field name - value pairs.

printableValueHash

Similar to the previous one, it returns a hash reference containing field name - printable value pairs.

The rows may be quite complex. An dumped from a result could clarify the subject:

Example 1.6. Rows dumped result

[
 {
  'order' => undef,
  'plainValueHash' => {
                       'members' => {
                                     ...
                                    },
                       'name' => 'RRHH',
                       'id' => 'x6666'
                      },
  'valueHash' => {
                  'members' => bless( {
                                       ...
                                      }, 'EBox::Types::HasMany' ),
                  'name' => bless( {
                                    'unique' => 1,
                                    'value' => 'RRHH',
                                    'printableName' => 'Name',
                                    'model' => $VAR1->[0]{'valueHash'}{'members'}{'model'},
                                    'HTMLSetter' => '/ajax/setter/textSetter.mas',
                                    'optional' => 0,
                                    'size' => '12',
                                    'row' => $VAR1->[0],
                                    'editable' => 1,
                                    'type' => 'text',
                                    'HTMLViewer' => '/ajax/viewer/textViewer.mas',
                                    'fieldName' => 'name'
                                    }, 'EBox::Types::Text' )
                 },
  'values' => [
               $VAR1->[0]{'valueHash'}{'name'},
               $VAR1->[0]{'valueHash'}{'members'}
              ],
  'id' => 'x6666',
  'printableValueHash' => {
                           'members' => {
                                         'model' => 'MemberTable',
                                         'values' => [
                                                      {
                                                      'macaddr' => undef,
                                                      'name' => 'Claudia',
                                                      'id' => 'memb44',
                                                      'ipaddr' => '192.168.1.92/32'
                                                      }
                                                     ],
                                         'directory' => 'objectTable/keys/x6666/members'
                                        },
                           'name' => 'RRHH',
                           'id' => 'x6666'
                         },
  'readOnly' => 0
  },
];

        

With the ease of use in mind, form data model allows accessing directly to the value, printable value attributes or the whole type using as method name the field name. For instance, at jabber dispatcher configuration form data model, you can access to the value, printable value and the instanced type itself of user property calling userValue, userPrintableValue and userType respectively.

Nevertheless, you may search for rows which match a fixed criterion, this implies the match is done against the field value. Four methods make the work for you, find, findAll, findValue and findValueAll. They are divided in two groups, the ones which returns the valueHash and the ones which returns the printableValueHash and has the 'Value' word in their names. Moreover, if the method ends with 'All' words, returns not just the first row which matchs but also the possible remainder rows which match the criterion as well. Taking the previous data model a search with $model->find( 'name' => 'RHHH' ), the dumped return value is:

{
 'members' => {
               'model' => 'MemberTable',
               'values' => [
                            {
                             'macaddr' => undef,
                             'name' => 'Claudia',
                             'id' => 'memb44',
                             'ipaddr' => '192.168.1.92/32'
                            }
                           ],
               'directory' => 'objectTable/keys/x6666/members'
              },
 'name' => 'RRHH',
 'id' => 'x6666'
}
        

Final part of developing with MVC framework is expose the desired API to other eBox modules, perl scripts and SOAP clients. Two main options are probable, just disclose the instanced data model allowing full access to the model using in the way we used above or integrate some helper methods to access to the most important features which clients use most. The latter is likely the chosen if the data model rises in its complexity. A module which consists of several data models connected with each other indicates that a simple API may help the module's clients.

1.5.4. When is this approach suitable?

As you may see, the MVC framework is powerful scheme to develop in eBox. However, it is not so impressive yet. It is still under development and some optimization as well as lack of some features. For instance, an eBox type to manage file uploading from the user, automatic getter and setter methods,...

Chapter 2. Basic API

2.1. EBox::Global

The EBox::Global class offers various module management functions. The most commonly used functions are those regarding module instantiation. EBox::Global works as a module factory, you get an instance of it using the getInstance static method and then you can use that instance to create modules. The factory comes in two flavors, a read-only flavor and a read-write one.

Calling getInstance without arguments will yield a read-write factory, which creates modules that let you make calls which change the configuration. A very important detail of read-write modules is that they return their latest configuration information, even if it has not been saved (which means that it could be revoked later by the user). This idea is important, configuration info reported by a read-write module should not be treated as final, unless that module has no changes waiting to be saved. You can see if a module has unsaved changes by calling the modIsChanged method in the EBox::Global class.

The idea behind the read-write modules behavior is to make it easy to build a configuration front-end. For example, if the user creates a new network object in the objects module, the new object will show up instantly in the firewall configuration, so he can create new firewall rules that use it. After all the desired changes have been made, the user saves the configuration. If he decides to cancel the changes he just made, both the new object and the firewall rules will be deleted.

There is one situation when you don't want to get information that has not been saved yet. That's when you are generating the configuration file for a daemon, setting up firewall rules, setting the address for a network interface, or any other activity that needs the real, final configuration info. This situation happens in system scripts (the boot script, cron jobs, etc). In these cases you need what we call read-only modules, they only report saved information and they do not allow method calls that change the configuration of the module. To obtain read-only modules you create EBox::Global instance setting its readonly parameter to true. Module instances returned by a factory created in this way will be read-only ones. This script shows how to get an instance of the squid module and tell it to restart itself:

Example 2.1. Creating a read-only module instance

#!/usr/bin/perl

use strict;
use warnings;

use EBox;
use EBox::Global;
use Error qw(:try);

EBox::init();

my $global = EBox::Global->getInstance(1);
my $squid = $global->modInstance('squid');

try {
	$squid->restartService();
} catch EBox::Exceptions::Base with {
	print STDERR "Squid module failed to restart.\n";
};

There are two functions that make it easy to perform two common tasks: getting an instance of every module and getting and instance of every module that implements some abstract class. These are modInstances and modInstancesOfType. Using them is straight forward:

Example 2.2. Functions for instantiating more than one module

my $global = EBox::Global->getInstance(1);

# restart all modules
foreach my $mod (@{$global->modInstances()}) {
	$mod->restartService();
}

# restart only modules that implement the NetworkObserver class
foreach my $mod (@{$global->modInstancesOfType('EBox::NetworkObserver')}) {
	$mod->restartService();
}

Restarting all the modules is even easier than that, EBox::Global provides the restartAllModules just for that. It's one of the methods that perform an operation on all the installed modules:

restartAllModules

Calls the restartService method on all the modules. This has the effect of restarting all the services handled by eBox. Config files are regenerated every time a service is restarted.

stopAllModules

Calls the stopService method on all the modules, this includes the web based administration interface, so be careful.

revokeAllModules

Cancels the configuration changes that have been made in all the modules since the last time the configuration was saved.

saveAllModules

Saves the config changes in all modules.

2.2. Exceptions

All error handling in eBox is implemented with java-style exceptions. These are provided by the perl module Error, you can check the perl documentation for that module for a detailed description on how to use exceptions in perl. eBox exceptions are defined in the EBox::Exceptions namespace.

eBox provides a hierarchy of exceptions from which you can choose the most appropriate for each situation. All eBox exceptions inherit from one of these two exceptions:

  • EBox::Exceptions::External

  • EBox::Exceptions::Internal

The difference between these two kinds is what the user will see when an uncaught exceptions occurs. The user interface framework will show the message contained in the exception unaltered if the exception is an external one. You should use external exceptions for user induced errors, like a syntax error in an IP address given by the user interface to the module backend.

If the exception is internal it will log the error and assume something is wrong internally in eBox (a bug) or in the system in general. When an internal exception is caught by the GUI framework the user will see a generic error message, recommending him to check the system logs or seek technical support. Any exception, internal or external, may be used internally for other purposes, as long as it is caught before it goes all the way up to the GUI framework.

The internal exceptions developed are:

EBox::Exceptions::Command

Exception raised when there was an error launching a command, that is, its returned value is different from zero. EBox::Exceptions::Command::Sudo does the same but with a sudo command.

EBox::Exceptions::DataInUse

Exception raised when there is a data in eBox which it is about to be removed which it is being used by another part of eBox.

EBox::Exceptions::DeprecatedMethod

Exception raised when a deprecated method has been called at runtime.

EBox::Exceptions::InvalidType

Exception raised when the type of an argument is not the expected one.

EBox::Exceptions::Lock

Exception raised when a module cannot get the lock.

EBox::Exceptions::MissingArgument

Exception raised when a compulsory argument is missing in a method call

EBox::Exceptions::NotImplemented

Exception raised when a method is not yet implemented. It could be used in an Abstract class in order to simulate its behaviour.

The external exceptions developed are:

EBox::Exceptions::DataExists

Exception raised when a user wants to add an element to eBox which already exists.

EBox::Exceptions::DataMissing

Exception raised when a user ignores a compulsory element which has to be filled to apply the configuration change.

EBox::Exceptions::DataNotFound

Exception raised when a user searches for an element which does not exist in eBox.

EBox::Exceptions::InvalidData

Exception raised when a user enters a value for a data which is invalid. An advice to the user may be set.

If you can't find an exception for certain error condition, you have two options: create a new exception class, inheriting from either EBox::Exceptions::External or EBox::Exceptions::Internal, or just use one those two classes as generic exceptions:

Example 2.3. Throwing a generic internal exception

if ($foo_condition) {
	throw EBox::Exceptions::Internal('Something happened!');
}

As you can see, throwing exceptions follows a syntax very similar to Java's. Catching them is very similar too:

Example 2.4. Catching an exception

use Error qw(:try);

sub foo
{
	my $bar = shift;

	try {
		$bar->doSomething();
	} catch EBox::Exceptions::Base with {
		# do nothing, just ignore the error
	};
}

You can do whatever you like inside the “catch” clause. You may also write several “catch” clauses if you need to do different things for different exception types. “otherwise” and “finally” clauses are also allowed, for a detailed explanation about them just run perldoc Error. A very important detail is not to forget the semicolon after the last clause (the “catch” in the example), failing to do so may produce very weird results, this is due to the black magic that is used in the implementation of the try-catch idiom.

2.3. Data validation

It is very important that every function exposed by each module validates its input data correctly. All of the arguments need to be checked, if this wasn't done a module could save syntactically wrong values in its configuration, and the underlying service would behave in an unpredictable way with the configuration file generated by the module. This also helps find bugs in the GUI front-end, and any other code that uses the module.

The EBox::Validate perl module provides a bunch of functions to validate different types of data. All of this functions work the same way, their first argument is the value that's going to be checked. They return true if the value is correct, undef if it's not:

Example 2.5. Using data validation functions

use EBox::Validate qw(:all);

my $ip = '192.168.0.1';

unless (checkIP($ip))
	print STDERR "$ip is invalid.\n";
}

The usual thing to do if an argument is incorrect is to throw an exception. And most of the time the incorrect value will be an user-supplied one. For this reasons, all the functions in EBox::Validate provide an easy mechanism for throwing EBox::External exceptions. All you have to do is pass one more argument to them, with a name or description for the value you are trying to check. If you pass that argument and the validation fails, an exception will be thrown with a message indicating the name of the wrong field:

Example 2.6. Using data validation functions with automatic error handling

checkIP($ip, 'IP address');

If you don't want an exception to be thrown, or if you want to throw a different kind of exception, just do not pass the last argument and handle the error yourself.

2.4. i18n

eBox uses gettext, the GNU project's internationalization (i18n) platform. It makes it easy for translators to translate the eBox user interface into their own language.

This section describes i18n from the point of view of both the developer (what steps must be taken to build a translatable module) and the translator (how to translate eBox into a new language).

2.4.1. i18n developers

Every eBox module has it's own gettext domain for the text strings it owns, the domain name is derived from the module name. Modules must set their gettext domain both in their constructor and in each one of their CGIs as explained in Section 3.1.1 and Example 5.7 respectively.

All files that contain translatable strings, including mason templates, must include the EBox::Gettext perl module. Also, every translatable string must be marked as such, using the __ function as the following example shows:

print __("Hello world");
			

This will enable the eBox build system to detect and extract all the translatable strings.

If a string includes a variable you should not concatenate it:

print __("Edit ") . $group . __(" members"); # wrong
			

This would make it impossible to correctly translate the whole sentence. The right way to do it is to use the __x function, which is a variant of __ which lets you include variables:

print __x("Edit {group} members", group => $group); # right
			

This way the translator can place the variable group in the right position in the sentence.

If you want to mark a string as translable but determining not to translate it, you should use __n function. An example of usage is the following:

Example 2.7. Using __n function

my $options = [__n('Foo'), __n('Bar'), __n('Foobaz') ]; # As many as you want
print __($options[$index]);
			  

The example showed above gives the chance to translator to translate only the option selected leaving the remainder (a high number normally) without a translation.

If you follow this rules correctly, the build system will automatically generate PO files with all translatable strings included. These PO files can be easily translated, as explained in the next section.

2.4.2. i18n for translators

Using GNU gettext as an i18n platform means we have the most widely used format for translations: PO files. Every eBox module has its one PO file for every available language. A translator just needs to translate all strings listed in that file in order to have a completely translated module. In case you are starting a translation for a new language from scratch, all you need to do is ask for a PO file to be generated for your language.

A PO file contains the text strings in the original language (English) along with their translations. Each string may be in one for three possible states: translated (a translation exists for that string), fuzzy (a translation exists, but the original string has changed slightly) or untranslated:

# translated string
msgid "Name"
msgstr "Nombre"

# fuzzy string
#, fuzzy
msgid "Interfaces"
msgstr "Interface"

# untranslated string
msgid "External"
msgstr ""
			

If the string includes the value of a variable when it is shown in the user interface you will see an entry like this one in the PO file:

msgid "Edit {group} members"
			

In this case you just have to rewrite the sentence placing the name of the variable (in curly braces) in its proper place, but without translating it. The correct translation for this string in Spanish would look like this:

msgstr "Editar miembros de {group}"
			

The preferred way to translate PO files is using our Web interface which is developed by Pootle, a Web application introduced in order to lower the barrier to start translating eBox.

Even though it's possible to translate a PO file using any UTF-8 enabled text editor or some of the created specifically for this task, such as KBabel (*nix) or poEdit (multi-platform). This is the unrecommended way to translate, however, it could be useful for translators which have to start its home localisation from scratch sending the po files to the eBox team after editing with these tools.

Chapter 3. Module backend

The eBox framework offers modules several functionalities that are made available through a mostly object-oriented API. Not everything is object-oriented, procedural interfaces have been used where it made sense to do so.

Some of the features of the API work through inheritance, these usually provide a means for the module to implement standard functionalities like menus, the status page, configuration saving and revoking, etc. Some of these functionalities need to be implemented by the module, others are given by the framework for free, and the module may override or extend them.

3.1. The base module

All eBox modules inherit from the EBox::Module class, this class defines abstract methods that modules may override to implement certain functionality. These methods are called by the framework when they are needed.

Besides those abstract methods, the class implements a few methods, providing some basic functionality. These methods often follow the template method design pattern, they perform some operation but delegate some part of it on some abstract method that may be implemented by child classes.

Finally, there are a few methods that implement some common operations that get used in most of the modules, these are meant to be called by children classes when they need them. They are located in the EBox::Module class just for convenience.

3.1.1. Module constructor

All module instances are created and cached by the EBox::Global class, module constructors must not be called directly except by EBox::Global. For this reason, they are named _create instead of new.

The leading underscore in the method name is a naming convention, methods with such a name are meant to be used privately by the owner class and its ancestors, they should not be called directly by an unrelated class. In the case of the module constructor, the only class that should call it is EBox::Global.

The _create method in EBox::Module takes two named arguments from the child classes. name is the name of the module, it is required; domain is the gettext domain for the module, it's optional and “ebox” is its default value.

Example 3.1. Simple module constructor

sub _create
{
	my $class = shift;
	my $self = $class->SUPER::_create(name => 'dhcp',
					domain => 'ebox-dhcp',
					@_);
	bless ($self, $class);
	return $self;
}

3.2. Root commands

The apache server under which eBox runs, and any Perl script that uses the eBox API run under a dedicated user id, the user is typically called “ebox”. eBox modules need to execute certain commands and write certain files with root privileges, this is done using sudo

You can invoke any command using the root() function within the EBox::Sudo Perl module. If the command fails, root() throws an EBox::Exceptions::Sudo::Command exception, so make sure you catch it if it is OK for the command to fail or if you want to inform the user in a different way. Anyway, you can use the output, error and exitValue exception methods to gather more information about the command failure. In the rare cases when the sudo program itself fails the exception raised is one of the EBox::Exceptions::Sudo::Wrapper kind and the last methods are not available.

Example 3.2. Using EBox::Sudo::root()

try {
  EBox::Sudo::root("/usr/bin/eject -t");
}
catch EBox::Exceptions::Sudo::Wrapper with {
  throw EBox::Exceptions::Internal("sudo program failed");
}
catch EBox::Exceptions::Sudo::Command with {
  my ($ex) = @_;
  EBox::debug("/usr/bin/eject failed. Stderr was " . (join "\n", @{ $ex->error() })) ;
  throw EBox::Exceptions::External("Cannot close CD-ROM tray. Please close it manually" );
}

EBox::Sudo also provides wrappers for frequent used commands executed with root privileges. stat provides a statistic wrapper, useful to get file system information for any file. If you need a more concise file information you can use fileTest to check files for simples queries like /usr/bin/test does.

3.3. EBox::GConfModule

eBox uses GConf to store its configuration, the development framework provides a wrapper around the original perl bindings. GConf gives us an easy API for storing and retrieving typed configuration values organized hierarchically. It also lets us define limited schemas for certain configuration keys, setting their type and default values.

The development framework defines a wrapper around the GConf API that provides some extra features. The wrapper is implemented as child class for EBox::Module, so all modules that want to use GConf inherit from EBox::GConfModule. Its children automatically get these features:

  • Automatic backups: the first time a key's value is changed, a backup of the configuration tree for the whole module is made. When the configuration is saved, the backup is automatically deleted, when the configuration changes are revoked the backup is automatically restored. EBox::GConfModule implements the makeBackup, restoreBackup and revokeConfig methods defined in EBox::Module, so EBox::GConfModule children do not need to implement them unless they have really special requirements or store part of their configuration outside of GConf.

  • Error handling: GConf errors are handled by the wrapper class, it translates them into internal exceptions.

  • Read-only and read-write instances: when the module is instantiated in read-only mode, the wrapper class uses a copy of the configuration to avoid seeing unsaved changes. It also prevents calls to methods that write to GConf.

  • Namespace boundary checks: the wrapper checks all gconf keys used by the module to see if they are in its namespace. This ensures that the keys for a module are only directly read or written to by the module itself (as long as all modules use the wrapper class).

  • Relative pathnames: methods in the wrapper class take both absolute and relative pathnames for gconf keys. The root of a module's namespace changes depending on the type of instance (read-only of read-write) and the type of key (normal or status) being accessed. For this reason it is best to use relative pathnames when calling the wrapper class methods, they translate the relative path to an absolute one automatically.

  • Status namespace: the is certain information that's not given by the user and needs to be written at any time, even by read-only instances. This kind of information is not subject to configuration saving and revoking. It is status information, like the name servers or IP address given to the system by an external DHCP server. A separate namespace is provided for this kind of information, and again it is automatically kept out of the backup/save/revoke operations mentioned earlier. The methods use to access this namespace are identical to the usual ones, they just have the prefix st_ in their names.

  • Recursive retrieval of directories: there are two methods in EBox::GConfModule that allow easy retrievals of whole directory structures. hash_from_dir takes a directory as an argument and returns a hash with all the keys under it. array_from_dir takes a directory as an argument and returns an array of hashes as returned by hash_from_dir for each one of its subdirectories.

This is a list of the most important methods in EBox::GConfModule:

all_dirs

Given a key it returns all directories within.

all_dirs_base

Given a key it returns all directories within, removing any leading directory component.

all_entries

Given a key it returns all entries within. Entries are all those keys which are not directories, hence they contain a value.

all_entries_base

Given a key it returns all entries within, removing any leading directory component. Entries are all those keys which are not directories, hence they contain a value.

array_from_dir

Given a key it returns an array using a hash reference to contain in each element the directories under the key. Also, the hash contains the key _dir which tells you the directory's name.

dir_exists

Given a key referencing a directory it returns true if it exists.

get_bool

Returns the value of a boolean key.

get_int

Returns the value of a integer key.

get_list

Returns an array containing the list referenced by the key.

get_string

Returns the value of a string key.

get_unique_id

It generates a unique random identifier with a leading prefix in the root of the module's namespace, if directory is passed, it will be added to the path. Note that it does not create the entry, it just returns a unique identifier, so it is up to you to create the proper entry.

hash_from_dir

It returns a hash containing all the entries in the directory referenced by the key.

isReadOnly

It returns true if the current EBox::GConfModule instance you are accessing is read-only.

makeBackup

It dumps your current configuration to file.

restoreBackup

It restores the last backup.

revokeConfig

All changes done since your first write or delete will be dismissed.

set_bool

It sets a key with a boolean value.

set_int

It sets a key with an integer value.

set_list

It sets a list of typevalues in value.

set_string

It sets values of type in key.

Let's see some examples from the above functions.

Example 3.3. Setting a string key

 $self->set_string("printers/x4235/name", "fooprinter"); 

Example 3.4. Setting a string list

$self->set_list('foo/foolist', 'string', ['foo', 'bar']);

Example 3.5. Getting and using a unique identifier

my $id = $self->get_unique_id('p', 'printers');
$self->set_string("printers/$id/name", $name);
$self->set_bool("printers/$id/configured", undef);
		

3.4. Ordering stuff

The need to keep certain information ordered is fairly common across eBox modules. It is also common to provide reordering functions. An example of this are firewall rules, which need to be applied in a given order, and the user has to be able to change their order. The EBox::Order class solves just this problem.

The idea is to give a directory to each item you want to keep ordered. Following the firewall example, we could have a rules/ directory and, below it, one directory per rule. Each rule would get a unique identifier, and that would be its directory name below rules/. A rule with id r3561 would be stored in rules/r3561, and below that directory we would store keys with each one of the rule's parameters. This is the most natural way of organizing items such as firewall rules, and it is with this organization that EBox::Order is designed to work.

The ordering mechanism adds one field to the items being ordered. Not surprisingly it is called order. To use the ordering API you have to create an EBox::Order instance. Its constructor takes to arguments: the instance of the module that owns the items being ordered, and the base directory where the items are stored.

EBox::Order implements these operations:

highest

Returns the highest order key of all the items.

lowest

Returns the lowest order key of all the items.

nextn

Given a number, returns the order key for the next item.

prevn

Given a number, returns the order key for the previous item.

get

Returns the identifier for the item whose order key equals a given number.

swap

It finds the items whose order keys match two given numbers and swaps their values.

list

Returns a reference to an array that holds the identifiers of all items, ordered from lowest to highest order.

Example 3.6. Ordering firewall rules

Let's see how the firewall module uses EBox::Order to keep its forwarding rules ordered. The _fwdRulesOrder returns the EBox::Order instance for the firewall rules:

sub _fwdRulesOrder
{
	my $self = shift;
	return new EBox::Order($self, "fwdrules");
}

fwdrules is the directory that holds all the rules. Another private helper function is _fwdRuleNumer, it returns the order number for a given rule identifier:

sub _fwdRuleNumber # (rule)
{
	my ($self, $rule) = @_;
	return $self->get_int("fwdrules/$rule/order");
}

New rules are appended at the end of the list, so we find the highest order number and add one to it, this code is part of the addFwdRule method:

my $order = $self->_lastFwdRule() + 1;

$self->set_string("fwdrules/$id/name", $id);
$self->set_string("fwdrules/$id/action", $action);
$self->set_bool("fwdrules/$id/active", 1);
$self->set_int("fwdrules/$id/order", $order);

_lastFwdRule is a trivial wrapper that returns the highest ordered number:

sub _lastFwdRule
{
	my $self = shift;
	my $order = $self->_fwdRulesOrder();
	defined($order) or return 0;
	return $order->highest;
}

Finally there are two methods to allow rule reordering, they are FwdRuleUp and FwdRuleDown (only the first one is shown here since they are almost identical):

sub FwdRuleUp # (rule)
{
	my ($self, $rule) = @_;
	my $order = $self->_fwdRulesOrder();
	defined($order) or return;
	my $num = $self->_fwdRuleNumber($rule);
	if ($num == 0) {
		return;
	}
	my $prev = $order->prevn($num);
	$order->swap($num, $prev);
}

3.5. Controlling a daemon

Besides exposing an API that allows reading and changing the configuration of a given service, a module's backend is in charge of making that service work. Typically, the service will be some sort of daemon that offers some functionality across the network after reading a configuration file. So your module needs to generate the configuration file and start/stop/restart the daemon at the right times.

3.5.1. Configuration file generation

The easiest way to generate configuration files is using the mason templating engine which is also used in the web front-end. Using mason templates is documented in Section 5.2 so we won't repeat it here.

Mason templates for configuration files are installed in the stubs directory under the shared directory for eBox. In the module source tree they are usually placed in directory of their own called stubs too. The Makefile.am in the stubs directory for the dns-cache module looks like this:

Stubdir = @STUBSPATH@/dns-cache

nobase_Stub_DATA = named.conf.mas named.conf.options.mas \
	named.conf.local.mas

EXTRA_DIST = $(nobase_Stub_DATA)

MAINTAINERCLEANFILES = Makefile.in

The autoconf macro included in ebox.m4 automatically exports the stubs directory path as STUBSPATH, so you just need to put create a directory for your module in it and put your mason templates there.

There is a method in EBox::Module that helps with file permissions and other details. It is called writeConfFile and it takes three arguments:

  • The path of the configuration file that's going to be generated.

  • The path of the mason template relative to the stubs directory.

  • A reference to the arguments that you want to pass to the mason template.

writeConfFile will generate the configuration file in a temporary location and then copy it on top of the desired destination, keeping its original permissions and ownership.

Example 3.7. Generating a configuration file

This is the code that generates the configuration file in the NTP module:


my $self = shift;
my @array = ();
my @servers = $self->servers;
my $synch = 'no';
my $active = 'no';

($self->synchronized) and $synch = 'yes';
($self->service) and $active = 'yes';

push(@array, 'active'   => $active);
push(@array, 'synchronized'  => $synch);
push(@array, 'servers'  => \@servers);

$self->writeConfFile(NTPCONFFILE, "ntp/ntp.conf.mas", \@array);

				

And this is the template the generates the ntp.conf file:


<%args>
	$active
	$synchronized
	@servers
</%args>	
# /etc/ntp.conf, configuration for ntpd
# Generated by EBox

driftfile /var/lib/ntp/ntp.drift
statsdir /var/log/ntpstats/

% if ($synchronized eq 'yes') {
%	if ($servers[0]) {
server <% $servers[0] %>
%	}
%	if ($servers[1]) {
server <% $servers[1] %>
%	}
%	if ($servers[2]) {
server <% $servers[2] %>
%	}
% }
% if ($active eq 'yes') {
server 127.127.1.0
% }
fudge 127.127.1.0 stratum 13

restrict default kod notrap nomodify nopeer noquery

restrict 127.0.0.1 nomodify

				

3.5.2. Controlling execution

The first thing you need to know is when to start and stop the daemon you are controlling. Services are started by calling the restartService or save methods on a module instance, however those methods are implemented by EBox::Module and should normally not be overridden, since they takes care of logging and/or saving the configuration changes. Both of these methods call an abstract method when they need to actually start/restart the service. This method is _regenConfig, and it's the one that you need to implement.

_regenConfig should generate the configuration files for the daemon and start or restart it. A sample implementation of this method can be found in Example 3.8. If you need to know whether the call is caused by a service start/restart or by saving the configuration changes, you can check the parameters passed to _regenConfig. When it's called because the configuration was saved (save()) the named argument save will be set to 1, if it is just a regular service restart (restartService()), the restart argument will be set to 1. In most situations you won't need this, since you can easily know whether the daemon is running, this is only useful for special cases like the network module.

When _regenConfig gets called, you'll probably need to know whether to start or restart the daemon, because choosing the wrong operation may produce an error. To make that decision you need to know if the daemon is currently running. EBox::Module has two methods to make this task easier. If you know the process ID of the process you can use pidRunning, it receives a process ID as an argument. If you just know the name of the file where the daemon stored its process ID, you'll want to call pidFileRunning, which takes a file name, checks for a process ID in it and calls pidRunning. Both methods return true if the process is running and false if it's not.

Example 3.8. Sample _regenConfig implementation

sub _regenConfig
{
	my $self = shift;
	$self->_setBindConf;
	$self->_doDaemon();
}

sub _doDaemon
{
	my $self = shift;

	if ($self->service and $self->pidFileRunning(PIDFILE)) {
		$self->_daemon('reload');
	} elsif ($self->service) {
		$self->_daemon('start');
	} elsif ($self->pidFileRunning(PIDFILE)) {
		$self->_daemon('stop');
	}
}

sub _daemon # (action)
{
	my ($self, $action) = @_;
	my $command = BIND9INIT . " " . $action . " 2>&1";

	if ( $action eq 'start') {
		root($command);
	} elsif ( $action eq 'stop') {
		root($command);
	} elsif ( $action eq 'reload') {
		root($command);
	} else {
		throw EBox::Exceptions::Internal(
			"Bad argument: $action");
	}
}

Stopping the service is similar, you just check if it is running and if it is, then run the command that stops it. As with restartService, EBox::Module has a template implementation of the stopService method, which calls an abstract method when it's time to actually stop the service. The abstract method, _stopService is the one you need to implement.

Example 3.9. Sample _stopService implementation

sub _stopService
{
	my $self = shift;

	if ($self->pidFileRunning(PIDFILE)) {
		$self->_daemon('stop');
	}
}

3.6. gconf schemas

3.6.1. Minimal gconf schema

A gconf schema is a special gconf key that sets the type and default value for some other gconf key. They are stored in the gconf database under the /schemas directory.

You will need to create a gconf schema for you module. This is needed at least to integrate your module within the framework. By means of this schema your module could be instanced. So the first thing you should do is to associate the given name for your module and its proper perl module. Let's look at an example to clarify this idea a little further.

Say we have a module called foobar, and the implementation for this module lies in EBox::Foobar. Your gconf schema would look something like this:

Example 3.10. Minimal gconf schema

<gconfschemafile>
<schemalist>
	<schema>
		<key>/schemas/ebox/modules/global/modules/foobar/class</key>
		<applyto>/ebox/modules/global/modules/foobar/class</applyto>
		<owner>ebox</owner>
		<type>string</type>
		<default>EBox::Foobar</default>
		<locale name="C"/>
	</schema>
</schemalist>
</gconfschemafile>

Once you have done this, anytime you instance the module foobar using the method modInstance from EBox::Global, it will know that it has to load and instance the class EBox::Foobar.

Example 3.11. Creating a module instance

my $foobar = EBox::Global->modInstance('foobar');
$foobar->some_method();

3.6.2. Default values

Gconf schemas become useful to establish default values. These values will be used by the system when the user has not set a value for the corresponding key.

There is an obvious scenario in which you might be interested in using this feature. This is when your module is installed for the first time, it could come in handy to set default values for your initial configuration.

Let's illustrate this with a simple example. Imagine you are providing a module to manage a HTTP proxy. One of the configurable parameters is the listening port. You want the user to have the capability to change it, but also you wish to provide 3128 as an initial value. Gconf schema is the right place to do it. For this example we would use the key /schemas/ebox/modules/proxy/port.

Example 3.12. Setting a default value in gconf schemas

<schema>
	<key>/schemas/ebox/modules/proxy/port</key>
	<applyto>/ebox/modules/proxy/port</applyto>
	<owner>ebox</owner>
	<type>int</type>
	<default>3128</default>
	<locale name="C"/>
</schema>

3.6.3. Module dependencies

It is likely your module configuration depends on some other modules. If this occurs to your module, you will be interested in being notified anytime that there is a change in any of the modules you depend on.

Continuing with the example used earlier, consider you want to configure the proxy for listening solely on the internal interfaces. The network module is the one which deals with the network interfaces. So at the time of generating the configuration for the proxy we will ask the network module for the network interfaces configured as internal. This raises an obvious issue, your module should regenerate its configuration whenever the internal interfaces change, that is, when the network module's configuration is changed.

In order to express this relationship of dependence we will use your module's gconf schema as follows:

Example 3.13. Setting module dependencies

<schema>
	<key>/schemas/ebox/modules/global/modules/proxy/depends</key>
	<applyto>/ebox/modules/global/modules/proxy/depends</applyto>
	<owner>ebox</owner>
	<type>list</type>
	<list_type>string</list_type>
	<default>[network]</default>
	<locale name="C"/>
</schema>

The above snippet tells the module global that proxy depends on network. This way, global will run the method _regenConfig from EBox::Proxy when the network module's configuration changes.

3.7. Backups

As we pointed out earlier, all changes made on the keys stored in gconf are automatically backed up the first time a write or delete operation is carried on. Consequently, all these changes could be restored automatically if the user wishes to dismiss or restore them from a file.This is actually true as long as you use the methods provided by EBox::GConfModule to do so.

If your module has at least some part independent from GConf, it must override methods dumpConfig and restoreConfig.

Additionally, apart from module configuration if you want to add some other data concerned to the module which comprise user files, log content and so on; it must implement the methods extendedBackup and extendedRestore. Remember all these information will just save and restore whether user requests it explicitly. Let's show how with an example:

Example 3.14. Overriding backup functions

sub dumpConfig
{
  my ($self, $dir) = @_;
 
  $self->{ldap}->dumpLdapData($dir);
}


sub restoreConfig
{
  my ($self, $dir) = @_;

  $self->{ldap}->loadLdapData($dir);
}

ebox-logs module is intended to administer eBox logs. We wish to have the chance to back up its contents. As module configuration does not include these data, we are going to use extendedBackup and extendedRestore. Remember that this kind of data is only stored if user chooses it.

explicitly. The next example shows how ebox-logs implement these features:

Example 3.15. Overriding functions for extended backups


sub extendedBackup
{
  my ($self, %params) = @_;
  my $dir    = $params{dir};
  
  my $dbengine = EBox::DBEngineFactory::DBEngine();
  my $dumpFile = "$dir/eboxlogs.dump";

  $dbengine->dumpDB($dumpFile);
}


sub extendedRestore
{
  my ($self, %params) = @_;
  my $dir    = $params{dir};

  my $dbengine = EBox::DBEngineFactory::DBEngine();
  my $dumpFile = "$dir/eboxlogs.dump";

  $dbengine->restoreDB($dumpFile);
}

		  

3.8. Administrative logging

eBox can log every change made through its administrative interface, which is useful to know when something was changed or who changed it. Every eBox module should report every log message to give a comprehensive administrative logging support.

In order to do so, you must include a new package from eBox core EBox::LogAdmin in your module.

Example 3.16. Including LogAdmin package

use EBox::LogAdmin qw ( :all );

A new module attribute named title is used, you must add it to the create call. For instance:

Example 3.17. Creating an object with administrative logging support

my $self = $class->SUPER::_create(name => 'objects',
                    title => __n('Objects'),
                    domain => 'ebox-objects',
                    @_);

Furthermore, a hash is needed in the constructor with the actions your module will report with a nicely formatted message. Following the same example in EBox::Objects module:

Example 3.18. Adding actions to log


$self->{'actions'} = {};
$self->{'actions'}->{'addObject'} = __n('Added object {object}');
$self->{'actions'}->{'addToObject'} =
        __n('Added {name} ({ip}/{mask} [{mac}]) to object {object}');
$self->{'actions'}->{'removeObject'} = __n('Removed object {object}');

	    

For each action you want to log, which should be everything that can be done from web interface, you should follow these steps:

  1. Add an action mark to the function, like:

    sub addObject # (description)
    {
        #action: addObject
    	      
  2. Call logAdminDeferred or logAdminNow. logAdminDeferred puts the action in the database with the field committed set to false, and should be called for actions that don't take place until the changes are saved, that is, anything that gets stored in gconf. When you save changes, the actions are marked as committed, if you revert changes the actions are removed.

    logAdminNow puts the action in the database with the field committed set to true, and should be used for actions that take effect immediately like changing the password or adding a user.

    Both functions have the same signature:

    logAdminDeferred(module,action,params)
    logAdminNow(module,action,params)
    	      

    You should call it only when you're sure that the action has taken place, for example, for addObject:

    Example 3.19. Calling logAdminDeferred method

    $self->set_string("$id/description", $desc);
    logAdminDeferred('objects',"addObject","object=$desc");
    return $id;