FacebookTwitterFlickrYoutuberss

Version 1

From Zentyal Linux Small Business Server
Jump to: navigation, search

Index || < Prev | Next >


Contents

Version 0.1: simple field

In this version we will let users choose which port they want apache to listen on, 80 by default. We will set apache configuration according to this.

Data model

The first version of our module is going to be pretty simple. The only thing we will let the user change will be the port. Roughly speaking, the data model is composed of some fields, you have to decide what kind of data we will store in each field. To do this, we use some classes shipped with the framework which are called types.

These are some of the types you can currently find in Zentyal:

    • Text
    • Int
    • Boolean
    • Select
    • Port
    • Password
    • PortRange
    • Service
    • HostIP
    • Host
    • IPAddr
    • ...etc


Note that each one of these classes are namespaced with EBox::Types. Remember that in Perl, to import a class you have to use the keyword use.

Let's take a look at our current model. The code is in src/EBox/Apache2/Model/Settings.pm:

package EBox::Apache2::Model::Settings;

use base 'EBox::Model::DataForm';

use strict;
use warnings;

use EBox::Gettext;

use EBox::Types::Text;
use EBox::Types::Boolean;

# Method: _table
#
# Overrides:
#
#       <EBox::Model::DataTable::_table>
#
sub _table
{
    my ($self) = @_;

    my @fields = (
        new EBox::Types::Boolean(
            'fieldName' => 'booleanField',
            'printableName' => __('Example boolean field'),
            'editable' => 1,
            'help' => __('This field is an example.'),
        ),
        new EBox::Types::Text(
            'fieldName' => 'textField',
            'printableName' => __('Example text field'),
            'editable' => 1,
            'size' => '8',
            'help' => __('This field is another example.'),
        ),
    );

    my $dataTable =
    {
        'tableName' => 'Settings',
        'printableTableName' => __('Settings'),
        'pageTitle' => $self->parentModule()->printableName(),
        'defaultActions' => [ 'editField' ],
        'modelDomain' => 'Apache2',
        'tableDescription' => \@fields,
        'help' => __('This is the help of the model'),
    };

    return $dataTable;
}

1;

As you can see, there are no (uncommented) fields, so the module will fail to work properly. Let's make some changes:

    • We won't be using a Text or Boolean type at the moment, so we will get rid of them. Instead, we'll use a Port type.
    • As we only have one field, we will remove textField, and we will change the name of booleanField to listeningPort, and of course, we need to change its type to EBox::Types::Port. Don't forget to change use statement from Ebox::Types::Text to EBox::Types::Port.


Let's change our code to look like this:

package EBox::Apache2::Model::Settings;

use base 'EBox::Model::DataForm';

use strict;
use warnings;

use EBox::Gettext;

use EBox::Types::Port;

# Method: _table
#
# Overrides:
#
#       <EBox::Model::DataTable::_table>
#
sub _table
{
    my ($self) = @_;

    my @fields = (
        new EBox::Types::Port(
            'fieldName' => 'listeningPort',
            'printableName' => __('Listening port'),
            'defaultValue' => 80,
            'editable' => 1,
        ),
    );

    my $dataTable =
    {
        'tableName' => 'Settings',
        'printableTableName' => __('Settings'),
        'pageTitle' => $self->parentModule()->printableName(),
        'defaultActions' => [ 'editField' ],
        'modelDomain' => 'Apache2',
        'tableDescription' => \@fields,
        'help' => __('This is the help of the model'),
    };

    return $dataTable;
}

1;

Building and installing the module

Let's build the package to install our first module by running:

cd apache2 (if you are not there already)
zentyal-package

The above command will build a debian package with your new module. You will find the package in the apache2/debs-ppa directory. You will need to install this package on Zentyal using dpkg:

sudo dpkg -i zentyal-apache2_3.0_all.deb

Fire up your browser to check the results of this operation. You should now see a new menu entry called Apache2. Click on this entry. You will see something like this:

Image(just-installed.png)

Here is a tip, you should always check the syntax of the files you modify. Take into account you are using a dynamic language, and debugging errors might be difficult. Every time you modify or create a new Perl file you should run perl -c:

perl -c /usr/share/perl5/EBox/Apache2/Model/Settings.pm

Let's go back to the code:

my @fields =
(
    new EBox::Types::Port(
        'fieldName' => 'listeningPort',
        'printableName' => __('Listening port'),
        'defaultValue' => 80,
        'editable' => 1
    )
);

The above code shows a constructor for the class EBox::Types::Port being called. The arguments are:

    • fieldName: this is the name of the field that we will always use to deal with it.
    • printableName: this is the name that the user will see. Note that the string Listening port is passed to the function __() which is used to translate the string into the user language.
    • editable: this tells the framework if the user is allowed to change its value or not from the UI.
    • defaultValue: if this parameter exists, the field will be preloaded with this value, otherwise it will be empty

Writing the apache configuration

Zentyal commits its configuration changes as follows:

    • The user starts a new web interface session and makes some changes to the configuration.
    • The Save changes button on the top-right corner turns red to let the user know there are unsaved changes.
    • When the user clicks on that button to save changes, the framework iterates over all modules with unsaved changes preserving its dependency order to make them commit their new configuration. The relevant method call is EBox::Module::Service::_regenConfig.


_regenConfig can be overriden if we require some really specific behaviour, but its default behaviour should be good enough for most modules.

By default, _regenConfig does the following two things:

    • Calls the method _setConf(), which is in charge of writing the required configuration files from the information we have gathered through the web interface and should be overriden by our module
    • Calls EBox::Module::Service::_enforceServiceState, which takes care of making sure the services we are managing are running if needed and stopped otherwise.


If these facilities seem to be enough for your module, you only need to declare two functions, _setConf and _daemons in our main class.

If you recall, our main class is called Apache2.pm and lives under src/EBox/Apache2.pm in our development directory, or in /usr/share/perl5/EBox/Apache2.pm when the package is installed.

The user can set the value for the port. Now we have to set the apache configuration according to this value.

The apache configuration file we are going to modify is /etc/apache2/ports.conf. The structure of this file is pretty simple:

Listen 80

In its current version, Zentyal uses a template based system, mason [1], to generate new configuration files.

Let's create our template for this file. Our scaffolding script has already created a template in stubs/service.conf.mas. We have to modify it as follows:

<%args>
  $port
</%args>
Listen <% $port %>

You can read a bit about Mason templates if you wish, but in essence, the above template receives a parameter $port which will be used and replaced in the line starting with Listen.

Let's write a private function in our main class file (src/EBox/Apache2.pm) to use the template and overwrite /etc/apache2/ports.conf and override _setConf so our function is called. It would be something like this:

...

my $CONFFILE = '/etc/apache2/ports.conf'; 

...

# Method: _setConf                                                              
#                                                                               
# Overrides:                                                                    
#                                                                               
#       <EBox::Module::Base::_setConf>                                          
#                                                                               
sub _setConf                                                                    
{                                                                               
    my ($self) = @_;                                                            
                                                                                
    my $settings = $self->model('Settings');                                    
    my $port = $settings->value('listeningPort');                               
                                                                                
    my @params = (                                                              
        port => $port,                                                          
    );                                                                          
                                                                                
    $self->writeConfFile(                                                       
        $CONFFILE,                                                              
        "apache2/service.conf.mas",                                             
        \@params,                                                               
        { 'uid' => '0', 'gid' => '0', mode => '644' }                           
    );                                                                          
}

The name of our model is Settings. The next thing we do is to retrieve the stored value of our field called listeningPort. For now, it is enough to know that Settings model is a class that will allow us to access all of the fields in our model. value() is a method to get the value of the given field.

Now that we know how to fetch the value from our data model, we will just visit the method writeConfFile, this method is implemented by the EBox::Module::Base class which our module inherits from. The first parameter is the destination file to be overwritten with the output of our template. The second parameter is our template, templates are stored under /usr/share/ebox/stubs, and /apache2/service.conf.mas is relative to that path. The third and last parameter is an array reference with the parameters we pass to the template, in this case we only have one, port.

The other method, _daemons, is used to let the service framework know which daemons our module uses by returning an array with the required information. In our case it should look like this:

# Method: _daemons
#
#       Overrides EBox::Module::Service::_daemons
#
sub _daemons
{

    my $daemons = [                                                             
        {                                                                       
            'name' => 'apache2',                                                
            'type' => 'init.d',                                                 
            'pidfiles' => ['/var/run/apache2.pid']                              
        }                                                                       
    ];                                                                          
                                                                                
    return $daemons; 
}

The _enforceServiceState method will use this information and the method isEnabled() to launch and stop the daemons when required.

The _regenConfig function will be called every time we change our module configuration and when the machine boots for first time.

Zentyal developers love to party, and they also love to be nice and friendly to users. That is why you have to tell the framework that your module will modify a system file, in this case, /etc/apache2/ports.conf. By doing so, Zentyal will detect if the user has modified that file manually before overwriting, and will ask permission to do it as courtesy to our beloved users. In your main class you will have to override the usedFiles() function to report which files your module overwrites:

# Method: usedFiles
#
#   Override EBox::Module::Service::usedFiles
#
sub usedFiles
{
    return [
            {
              'file' => '/etc/apache2/ports.conf',
              'module' => 'apache2',
              'reason' => __('To configure the apache port')
            },
           ];
}

If the module is enabled we must overwrite /etc/apache2/ports.conf. To do so we use the convenience method we created earlier, _setConf(). After writing the configuration we restart apache. We use the static method EBox::Sudo::root() to run a command through sudo. In case the module is disabled by the user, we stop apache.

Install and try the first functioning version of your module!. In the next version we will extend it.


Fetching the current value from the Zentyal's shell

We already have a web interface with a simple form where the user can set the Apache port. To check the Zentyal's shell, enable the Apache2 module, change the default port and save changes. This way we will have the setting available in the shell.

You can query the changed value directly from the Zentyal's shell:

# /usr/share/zentyal/shell
zentyal> instance apache2
$apache2
zentyal> $apache2->model('Settings')->value('listeningPort');
80
zentyal>

The name of our model is Settings, but it's namespaced within our module name. You should recall, when we created the scaffolding, our module name was apache2.

The next thing we do is to retrieve the stored value of our field called listeningPort.

$apache2->model('Settings') is an instance of our model. It's a class that will allow us to access all of the fields in our model. value() is a method to get the value of the given field.

You can read more information about the Zentyal's shell at: http://trac.zentyal.org/wiki/Documentation/Community/Development/Tools


[1] <http://www.masonhq.com>


Index || < Prev | Next >

Template:TracNotice

Personal tools
Namespaces

Variants
Actions

Zentyal Wiki

Zentyal Doc
Navigation
Toolbox