8.2. Short way to create a small module

Previously in Section 8.1, a long way to create an eBox module has been explained, however in this section we are going to use the MVC framework described in Section 1.5. This will reduce our development time turning into a less error-prone and with a greater quality. Since less code is required to be written and this code will handle the compulsory logic avoiding to do repetitive tasks such as CGI or template creation as well as manage database backend directly letting developer focus on what really cares to add the new service to eBox.

As it was done in Section 8.1, we have chosen a service, in this case, the HyperText Transfer Protocol server. We have selected the Apache HTTP server version 2 to provide a simple glance of how powerful the MVC framework is to develop eBox agility and correctly. The web server eBox module has these following requirements:

These steps are going to follow to develop this module:

8.2.1. Define data model

In order to do the enable/disable web service, we can use the common form model which shows, store a boolean value to enable/disable service called EBox::Common::Model::EnableForm. So we delayed this feature till Section 8.2.3 where the instantiation is done.

The general apache configuration is a form which contains the port number which is a specific Port type and a Boolean type to enable users publish their own HTML pages under public_html directory from their own home directory. The description is displayed in Example 8.26 inside the EBox::WebServer::Model::GeneralSettings class.

Example 8.26. General configuration settings description

sub _table
{

    my @tableHeader =
      (
       new EBox::Types::Port(
                             fieldName     => 'port',
                             printableName => __('Listening port'),
                             editable      => 1,
                             defaultValue  => 80,
                            ),
       new EBox::Types::Boolean(
                                fieldName     => 'enableDir',
                                printableName => __x('Enable per user {dirName}',
                                                     dirName => PUBLIC_DIR),
                                editable      => 1,
                               ),
      );

    my $dataTable =
      {
       tableName          => 'GeneralSettings',
       printableTableName => __('General configuration settings'),
       defaultActions     => [ 'editField', 'changeView' ],
       tableDescription   => \@tableHeader,
       class              => 'dataForm',
       help               => __x('General Web server configuration. The listening port '
                                 . 'must not be got from another service. If you enable '
                                 . 'user to publish their own html pages, the pages will be '
                                 . 'loaded from {dirName} directory from their samba home directories',
                                 dirName => PUBLIC_DIR),
       messages           => {
                              update => __('General Web server configuration settings updated'),
                             },
       modelDomain        => 'WebServer',
      };

    return $dataTable;

}
          


The Port type supported by services eBox module allows us to show and set a port number which resides in its data models, checking itself that the given number is a valid port and, if the firewall module is installed, check its availability.

Once the general configuration is done, we are going to generate a list of virtual hosts to live inside our web server. As the module is a proof concept, only a name will be provided to generate a virtual host and additional check box to enable/disable the site. The document root is fixed and it is related to the virtual host's name and it is enabled by default. As it may live as many as we wish, a data model in a table is the best solution to address here. Its description is shown in Example 8.27 inside the EBox::WebServer::Model::VHostTable class.

Example 8.27. Virtual host data model description

sub _table
{
    my @tableHead =
        (
         new EBox::Types::Text(
                                fieldName     => 'name',
                                printableName => __('Name'),
                                size          => 12,
                                unique        => 1,
                                editable      => 1,
                             ),
        );

    my $dataTable =
      {
       tableName           => 'VHostTable',
       printableTableName  => __('Virtual hosts'),
       defaultActions      => ['add', 'del', 'editField',  'changeView' ],
       tableDescription    => \@tableHead,
       class               => 'dataTable',
       help                => __x('Virtual hosts are a form of web hosting service where '
                                  . 'many instances of the same web server is hosted on a '
                                  . 'single physical server. Different host names will point '
                                  . 'to the same web server. The DNS entry is automatically created'
                                  . ' if this is possible. The content must be placed under '
                                  . '{docRoot}/vHostName directory',
                                 docRoot => EBox::WebServer::PlatformPath::DocumentRoot()),
       printableRowName    => __('virtual host'),
       modelDomain         => 'WebServer',
       sortedBy            => 'name',
       enableProperty      => 1,
       defaultEnabledValue => 1,
      };

    return $dataTable;
}
            
          


As you may see, the enabled field is not directly set as another type. The framework allows to set enableProperty to one with a default value, namely defaultEnabledValue, and automatically a new field called enabled is added to the model as a EBox::Types::Boolean type.

As we want to order the virtual host by name, we must set the property sortedBy with the field name we want to order the table content.

One of our requisites is to handle DNS domains automatically provided that the DNS is installed. This is done whenever a virtual host is added to the model through addedRowNotify. We try to add the domain and the host name from a new added virtual host split the virtual host's name from the first dot, i.e. having www.example.com as virtual host's name the host name is www and the domain example.com. If the domain does exist, it tries to add a host name. However, if the guessed IP address is already taken, an alias to the owner host name is added. Regarding to the IP address assigned to that host, we try to guess it from any static interface starting with internal one through _guessWebIPAddr private helper method. The program listing is shown in Example 8.28.

Example 8.28. Adding name resolution to the virtual host

sub addedRowNotify
{
    my ($self, $row) = @_;

    # Get the DNS module
    my $gl = EBox::Global->getInstance();
    if (not  $gl->modExists('dns') ) {
        # no DNS module present, nothing to add then
        return;
    }

    my $dns = $gl->modInstance('dns');

    my $vHostName = $row->{plainValueHash}->{name};
    my ($hostName, $domain) = ( $vHostName =~ m/^(.*?)\.(.*)/g );

    # We try to guess the IP address
    my $ip = $self->_guessWebIPAddr();
    if ( $ip ) {
        if ( none(map { $_->{name} } @{$dns->domains()}) eq $domain ) {
            # The domain does not exist, add domain with hostname-ip mapping
            my $domainData = {
                              domain_name => $domain,
                              hostnames => [
                                            {
                                             hostname => $hostName,
                                             ip       => $ip,
                                            },
                                           ],
                             };
            $dns->addDomain($domainData);

            $self->setMessage(__x('Virtual host {vhost} added. A domain {domain} ' .
                                  'has been created with the mapping ' .
                                  'name {name} - IP address {ip} ',
                                  vhost => $vHostName,
                                  domain => $domain,
                                  name   => $hostName,
                                  ip     => $ip
                                 ));
        } else {
            my @hostNames = @{$dns->getHostnames($domain)};
            if ( none(map { $_->{name} } @hostNames ) eq $hostName ) {
                # Check the IP address
                my ($commonHostName) = grep { $_->{ip} eq $ip } @hostNames;
                unless ( $commonHostName ) {
                    # Add a host name
                    $dns->addHostName( $domain,
                                       hostname => $hostName,
                                       ipaddr => $ip);
                    $self->setMessage(__x('Virtual host {vhost} added. A mapping ' .
                                          'name {name} - IP address {ip} has been added ' .
                                          'to {domain} domain',
                                          vhost  => $vHostName,
                                          name   => $hostName,
                                          ip     => $ip,
                                          domain => $domain,
                                         ));
                } else {
                    # Add an alias
                    my $oldHostName = $commonHostName->{name};
                    try {
                        $dns->addAlias( "/$domain/$oldHostName",
                                        alias => $hostName);
                        $self->setMessage(__x('Virtual host {vhost} added as an alias {alias}'
                                              . ' to hostname {hostname}',
                                              vhost    => $vHostName,
                                              alias    => $hostName,
                                              hostname => $oldHostName));
                    } catch EBox::Exceptions::DataExists with {
                        $self->setMessage(__x('Virtual host {vhost} added',
                                              vhost => $vHostName));
                    }
                }
            } else {
                $self->setMessage(__x('Virtual host {vhost} added',
                                      vhost => $vHostName));
            }
        }
    } else {
        $self->setMessage(__('There is no static internal interface to ' .
                             'set the Web server IP address'));
    }

}

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

    my $netMod = EBox::Global->modInstance('network');

    my @ifaces = @{$netMod->ifaces()};

    @ifaces = grep { $netMod->ifaceMethod($_) eq 'static' } @ifaces;

    return '' unless (@ifaces > 0);

    my @intIfaces = grep { not $netMod->ifaceIsExternal($_) } @ifaces;

    if ( @intIfaces > 0 ) {
        return $netMod->ifaceAddress($intIfaces[0]);
    }

    my @extIfaces = grep { $netMod->ifaceIsExternal($_) } @ifaces;

    return $netMod->ifaceAddress($extIfaces[0]);

}
          


The setMessage method implies the returning message shown to the user when the action is made. We use it to inform about the new DNS domain, host name or alias added to an existing domain or nothing done since no static internal interface has been set up.

8.2.2. Define module web GUI using Composites

At this section, the layout display is explained and how easy and rapid the development process is. We want to show our previous defined models in a top bottom layout in this order: first, the enable/disable button for the web service, then the general configuration settings and, finally, the virtual host table. We assume the virtual host number for internal use will be small and it will match with the window height to see the web service configuration at one glance in the same page. To achieve so, we define a composite called EBox::WebServer::Composite::General which comprises the three elements in a top-bottom layout as Example 8.29 demonstrates:

Example 8.29. General composite to display Web service configuration page

sub _description
{

    my $wsMod = EBox::Global->modInstance('webserver');

    my $description =
      {
       components      => [
                           '/' . $wsMod->name() . '/EnableForm',
                           '/' . $wsMod->name() . '/GeneralSettings',
                           '/' . $wsMod->name() . '/VHostTable',
                          ],
       layout          => 'top-bottom',
       name            => 'General',
       printableName   => __('Web service'),
       compositeDomain => 'Web',
       help            => __('The eBox web service allows you ' .
                             'to host Web pages in plain HTML ' .
                             'within different virtual hosts'),
      };

    return $description;

}
          


As you may notice, only the model names are required to integrate the components inside the composite.

8.2.3. Publishing model and composite from this module

In order to let eBox see the models and composites defined above, i.e. publish them on eBox framework, so that they access and manipulate their data. Let's edit the main module class EBox::WebServer. To publish models and composites, we must override modelsClasses and compositeClasses methods from EBox::Model::ModelProvider and EBox::Model::CompositeProvider respectively as Section 1.5.3 describes.

Example 8.30. Publishing models and composites from web server module

sub modelClasses
{
  return [
	  {
	   class => 'EBox::Common::Model::EnableForm',
	   parameters => [
			  enableTitle => __('Web service status'),
			  domain => 'ebox-webserver',
                          modelDomain => 'WebServer',
			 ],
	  },
	  'EBox::WebServer::Model::GeneralSettings',

          'EBox::WebServer::Model::VHostTable',
	 ];
}
sub compositeClasses
{
 return [
            'EBox::WebServer::Composite::General',
         ];
}
          


With every method you must describe those models and composites you are going to use. The provider is in charge of instantiate the classes avoiding the developer to execute repetitive and error-prone tasks.

In those cases where the model constructor requires parameters, as it happens in Example 8.30, two elements are used per component, the class string and its parameters in a hash form using the parameter name as key. The EnableForm is a common model which is intended to be used in different modules to enable/disable a service. Because of that, its construction is a bit different from the other ones, since the model is parametrised with the gettext domain (domain), its title (enableTitle) and the model domain (modelDomain). The remainder two methods are general ones used along eBox the code.

Once the model is published, you can access the instances via these helper methods offered by providers, model and composite which given the model name, it returns the instance of that model.

8.2.4. Common tasks

The differences between long way and short way are almost done. On this final part, the main difference is the way to access the stored information. Traditional way allows accessing data stored at GConf directly, however with the MVC scheme the data is got through the data model. These steps are required to finish the module:

  • Generate the configuration files.

  • Control the daemon execution.

  • Make the module show up in eBox menu and status summary page.

  • Establish rules through services and firewall modules to use Web service in eBox.

Taking a look to the long way of a module creation, we can observe as we have removed any CGI or mason development. Inside this section the two steps of the three steps are going to be described in some detail.

What is known the eBox module "backend" is done in EBox::WebServer. Firstly, the constructor must indicate to eBox its name and Gettext domain as Example 8.31 shows.

Example 8.31. WebServer module constructor

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


Every time the Web service is restarted, the configuration is regenerated and the daemon(s) are restarted as well through the _regenConfig protected method. Its configuration is done as following:

Example 8.32. _regenConfig for web service

sub _regenConfig
{

    my ($self) = @_;

    $self->_setWebServerConf();
    $self->_doDaemon();

}
          


Both private methods _setWebServerConf, which sets up the web service configuration files, and _doDaemon, which manages the processes that develop the web services, are explained in the below sections and it is known as the module "backend".

8.2.4.1. Configuring Apache2 service

In order to manage the apache daemon and generate its configuration files, it is required to study the Apache web server and check if the features we wish to have in our service are available. The Apache2 web server is a quite complex software which handles several files to configure its service.

The listening port is set by /etc/apache2/ports.conf which contains the Listen directive to achieve so.

To enable the user directory, the mod_userdir is used. Its availability comes with the debian package apache2-common and it must be listed in /etc/apache2/mods-available directory as two files userdir.conf and userdir.load. We want to allow userdir feature to our LDAP users, to do so we require to include another module called mod_ldap_userdir which searches for home directory of LDAP users in their attributes on the cited database. This Apache2 plugin comes in the Debian package libapache2-mod-ldap-userdir.

In order to enable any apache2 module, it is required to create symbolic links to those configuration files inside /etc/apache2/mods-enabled directory. This process is done by a2enmod, the disable process is done analogously with a2dismod. The default configuration is set up in userdir.conf.

Related to the virtual hosts, every new virtual host has a predefined template to configure them and it is set under /etc/apache2/sites-available directory. Analogously to userdir module, the virtual host will be enabled when a symbolic link to that configuration file is placed in /etc/apache2/sites-enabled directory. As the configuration may be not enough for advanced users or LAMP installations, we create a directory to place the user defined configuration files with the following pattern user-ebox-$vhostName.

Similarly as done in Section 8.1.6, we create a private method _setWebServerConf in order to set up every template in the web server configuration as Example 8.33 whenever the web service is restarted.

Example 8.33. Generating web server configuration files

sub _setWebServerConf
  {

      my ($self) = @_;

      $self->_setPort();
      $self->_setUserDir();
      $self->_setVHosts();

  }
            


Every listed helper method sets up a certain part within the apache setup process. The first one sets the listening port, the second one enable/disable the user directory module and the last one it will set up every configured virtual host. The program listing from these modules is described in Example 8.34

Example 8.34. Setting up the web server

# Set up the listening port
sub _setPort
{

    my ($self) = @_;

    # We can assume the listening port is ready available
    my $generalConf = $self->model('GeneralSettings');

    # Overwrite the listening port conf file
    $self->writeConfFile(PORTS_FILE,
                         "webserver/ports.conf.mas",
                         [ portNumber => $generalConf->portValue() ],
                        )

}

# Set up the user directory by enable/disable the feature
sub _setUserDir
{

    my ($self) = @_;

    my $generalConf = $self->model('GeneralSettings');
    my $gl = EBox::Global->getInstance();

    if ( $generalConf->enableDirValue() ) {
        # User dir enabled
        foreach my $confFile (USERDIR_CONF_FILES) {
            unless ( -e AVAILABLE_MODS_DIR . $confFile ) {
                throw EBox::Exceptions::External(__x('The {userDirConfFile} ' .
                                                     'is missing! Please recover it',
                                                     userDirConfFile => AVAILABLE_MODS_DIR . $confFile));
            }
        }
        # Manage configuration for mod_ldap_userdir apache2 module
        if ( $gl->modExists('samba') ) {
            eval 'use EBox::Ldap; use EBox::UsersAndGroups;';
            my $rootDN = EBox::Ldap::rootDn();
            my $ldapPass = EBox::Ldap::getPassword();
            my $usersMod = $gl->modInstance('users');
            my $usersDN = $usersMod->usersDn();
            $self->writeConfFile( AVAILABLE_MODS_DIR . LDAP_USERDIR_CONF_FILE,
                                  'webserver/ldap_userdir.conf.mas',
                                  [
                                   rootDN  => $rootDN,
                                   usersDN => $usersDN,
                                   dnPass  => $ldapPass,
                                  ]);
            EBox::Sudo::root('a2enmod ldap_userdir');
        }
        # Enable the modules
        EBox::Sudo::root('a2enmod userdir');
    } else {
        # Disable the modules
        EBox::Sudo::root('a2dismod userdir');
        if ( $gl->modExists('samba')) {
            EBox::Sudo::root('a2dismod ldap_userdir');
        }
    }
}

# Set up the virtual hosts
sub _setVHosts
{

    my ($self) = @_;

    my $vHostModel = $self->model('VHostTable');

    my $vHosts = $vHostModel->rows();

    # Remove every available site using our vhost pattern ebox-*
    my $vHostPattern = VHOST_PREFIX . '*';
    EBox::Sudo::root('rm -f ' . SITES_AVAILABLE_DIR . "$vHostPattern");
    EBox::Sudo::root('rm -f ' . SITES_ENABLED_DIR . "$vHostPattern");

    foreach my $vHost (@{$vHostModel->rows()}) {
        # Access to the field values for every virtual host
        my $vHostValues = $vHost->{plainValueHash};

        my $destFile = SITES_AVAILABLE_DIR . VHOST_PREFIX . $vHostValues->{name};
        $self->writeConfFile( $destFile,
                              "webserver/vhost.mas",
                              [ vHostName => $vHostValues->{name} ],
                            );

        # Create the subdir if required
        my $userConfDir = SITES_AVAILABLE_DIR . 'user-' .  VHOST_PREFIX
          . $vHostValues->{name};
        unless ( -d $userConfDir ) {
            EBox::Sudo::root("mkdir $userConfDir");
        }

        if ( $vHostValues->{enabled} ) {
            # Create the symbolic link
            EBox::Sudo::root("ln -s $destFile " . SITES_ENABLED_DIR);
            # Create the directory content if it is not already
            # created
            my $dir = EBox::WebServer::PlatformPath::DocumentRoot()
              . '/' . $vHostValues->{name};
            unless ( -d $dir ) {
                EBox::Sudo::root("mkdir $dir");
            }
        }
    }

}
            


We have accessed to the data model using two different ways. With regard to the general configuration form, we have used the auto-loaded methods that a DataForm has to access the model values directly, for instance $generalConf->portValue() and $generalConf->enableDirValue(). Regarding to the virtual host data model, as we have to visit each row the usage of rows is compulsory. Furthermore, accessing to the helper structure plainValueHash since only the values are important when a configuration is regenerated. The internal logic creates every virtual host configuration from a single template and it is written under sites-available directory using the pattern: ebox-$virtualHostName. If the virtual host is enabled, a symbolic link is created under sites-enabled directory.

The ports.conf.mas is pretty simple as Example 8.35 shows. The doc part is set to indicate that everything inside them is documentation.

Example 8.35. ports.conf.mas template

<%doc>
  ports.conf.mas writes the listening port used by apache virtual host
  to serve Web pages.

  Parameters:

  portNumber - Int the port number to listen to Web requests

</%doc>
<%args>
  $portNumber
</%args>
Listen <% $portNumber %>
            


Regarding to vhost.mas, its content is almost as simple as the ports one as we see in Example 8.36.

Example 8.36. vhost.mas template

<%doc>
   Template to configure a simple named virtual host using the default
   site given with the apache2 debian package
   
   Parameters:

   vHostName - String the virtual host's name
</%doc>
<%args>
  $vHostName
</%args>
<%init>
  use EBox::WebServer;
  use EBox::WebServer::PlatformPath;
  my $vHostPrefix = EBox::WebServer::VHostPrefix();
  my $docRoot = EBox::WebServer::PlatformPath::DocumentRoot();
</%init>
NameVirtualHost *
<VirtualHost *>
        ServerAdmin webmaster@localhost

        ServerName <% $vHostName %>
        DocumentRoot <% $docRoot %>/<% $vHostName %>

        ErrorLog /var/log/apache2/error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog /var/log/apache2/access.log combined
        ServerSignature On

        # Under this directory you can manually configure every thing
        # you may need of that stuff
        Include sites-available/<%  'user-' . "$vHostPrefix$vHostName" %>/*
</VirtualHost>
            


Every virtual host may have an sites-available/user-ebox-$vHostName directory to include every manual configuration you may require.

8.2.4.2. Manage Apache2 daemon execution

Likewise it is done in Example 8.21, the web service is managed by runit and the private method to manage is called _doDaemon. The WEB_SERVICE constant contains the apache2 value to name the runit service afterwards.

Example 8.37. Web daemon management method

sub _doDaemon
  {

      my ($self) = @_;

      if ( $self->running() and $self->service() ) {
          EBox::Service::manage(WEB_SERVICE, 'restart');
      } elsif ( $self->service() ) {
          EBox::Service::manage(WEB_SERVICE, 'start');
      } else {
          EBox::Service::manage(WEB_SERVICE, 'stop');
      }

  }
            


The shell script to launch the web service tools/runit/apache2 contains this listing:

Example 8.38. tools/runit/apache2 file

            #!/bin/sh
            exec 1>&2
            exec /usr/sbin/apache2 -DNO_DETACH
            


In addition to the launcher, a finisher script, which is run whenever a service ends, is used in order to restart the service a fixed number of times in a time interval before ceasing. This is specially useful when a misconfiguration is done in any part of development or an incorrect daemon working. The ebox-runit-finisher requires two arguments:

eBoxModule

This set the eBox module name

serviceName

The runit service's name

The finisher script must follow this pattern: ${serviceName}.finish. In our example, the shell script tools/runit/apache2.finish lists as following:

Example 8.39. tools/runit/apache2.finish file

            #!/bin/sh
            exec ebox-runit-finisher web apache2
            


Whenever the web service is asked to stop by command line, i.e. /etc/init.d/ebox web stop, the _stopService is called to stop the service as we do using the EBox::Service::manage class method.

Example 8.40. Stop web service with _stopService method

sub _stopService
  {

      EBox::Service::manage(WEB_SERVICE, 'stop');

  }
            


8.2.4.3. Web service inclusion in eBox menu and summary

In order to show up the web server in the menu, it is almost the same procedure than Section 8.1.5. However, in this case we just add an item. The URL to set is going to show the view from the composite GeneralComposite that we have defined in Example 8.29. To do so, the URL value must follow this pattern: $compositeDomain/Composite/$compositeName as Example 8.41 describes.

Example 8.41. Adding WebServer to the menu

sub menu
  {

      my ($self, $root) = @_;

      my $item = new EBox::Menu::Item(name  => 'WebServer',
                                      text  => __('Web'),
                                      url   => 'WebServer/Composite/General',
                                     );

      $root->add($item);

  }
            


Regarding to status summary, the statusSummary method implementation is analogous to the one defined in Example 8.17 but swapping every NTP reference to Web.

8.2.4.4. Establish rules through services and firewall modules to use Web service in eBox

In order to establish the web service, we are going to add the http service to the services eBox module on the installation and add an internal rule to allow the users from internal network to access the service when it is enabled. As the listening port is configurable and it may differ from the default one, we may set the listening port on GeneralSettings model. This is configured at the firewall module through Firewall->Packet filter. It is required to develop two static methods, both are included in EBox::WebServer::Maintenance class to be run on installation and removal as Example 8.42 explains.

Example 8.42. onInstall and onRemove methods to configure web service

sub onInstall
{
    # Become an eBox user
    EBox::init();

    # Get service module
    my $gl = EBox::Global->getInstance();
    my $serviceMod = $gl->modInstance('services');
    my $firewallMod = $gl->modInstance('firewall');
    my $port = 80; # Default port = 80

    # Add http service
    unless ( $serviceMod->serviceExists('name' => 'http')) {
        # Check port availability
        my $available = 0;
        do {
            $available = $firewallMod->availablePort('tcp', $port);
            unless ( $available ) {
                if ( $port == 80 ) {
                    $port = 8080;
                } else {
                    $port++;
                }
            }
        } until ( $available );
        $serviceMod->addService(
                                'name'            => 'http',
                                'description'     => __('HyperText Transport Protocol'),
                                'protocol'        => 'tcp',
                                'sourcePort'      => 'any',
                                'destinationPort' => $port,
                                'internal'        => 1,
                                'readOnly'        => 1,
                               );
        $firewallMod->setInternalService(
                                        'http',
                                        'accept',
                                       );
        # Save the changes
        $serviceMod->save();
        $firewallMod->save();
    } else {
        EBox::info('The http service is already exists, not adding');
        my $servId = $serviceMod->serviceId('http');
        $port = $serviceMod->serviceConfiguration($servId)->[0]->{destination};
    }
    # Save settings on the model
    my $webMod = $gl->modInstance('webserver');
    my $settingsModel = $webMod->model('GeneralSettings');
    $settingsModel->set(port => $port);
    $webMod->save();

}
sub onRemove
{
    # Become an eBox user
    EBox::init();

    my $serviceMod = EBox::Global->modInstance('services');

    if ($serviceMod->serviceExists('name' => 'http')) {
        $serviceMod->removeService('name' => 'http');
    } else {
        EBox::info("Not removing http service as it already exists");
    }

    $serviceMod->save();
}
            


The default port number to use is 80, however it could be used by another service like the administration one. This is the reason to check any available port on the installation. When an available port is found, it is set on GeneralSettings model instead of using the default value given in model description. These methods are required to be executed as eBox user since both are called on the installation scripts. This is not done automatically and it is required to be called explicitly as Example 8.43 shows on debian/ebox-web.postinst and debian/ebox-web.prerm.

Example 8.43. Debian scripts

# ebox-web.postinst script
...

onInstall() {

    perl -e 'use EBox::WebServer::Maintenance; EBox::WebServer::Maintenance::onInstall();'
}

case "$1" in
	configure)
                ...

		# don't start Web server with its script	
		update-rc.d -f apache2 remove

		onInstall

		invoke-rc.d ebox webserver restart
                invoke-rc.d ebox apache restart
esac

#DEBHELPER#

exit 0

# ebox-web.prerm script
...

#DEBHELPER#

onRemove() {

	perl -e 'use EBox::WebServer::Maintenance; EBox::WebServer::Maintenance::onRemove();'
}

case "$1" in
	upgrade)
		remove_gconf_schemas
		;;
	remove)
		onRemove
		/etc/init.d/ebox webserver stop || true
		remove_gconf_schemas
		;;	
esac
            


As you may see, it is required to remove any previous apache2 daemon establishment in order to manage apache2 server just by eBox.

8.2.5. Expose module API

The module has two options to expose the configuration settings. Firstly, it may open up every model and let the module client access directly to the data model or, secondly, it gives some common methods to access the data easily. The latter option is chosen by this tutorial.

It seems useful to have a method to get the virtual hosts that already exist in eBox, this is done by virtualHosts method.

Example 8.44. virtualHosts method

sub virtualHosts
{

    my ($self) = @_;

    my $vHostModel = $self->model('VHostTable');
    my @vHosts;
    foreach my $rowVHost (@{$vHostModel->rows()}) {
        my $values = $rowVHost->{plainValueHash};
        push ( @vHosts, {
                         name => $values->{name},
                         enabled => $values->{enabled},
                        });
    }

    return \@vHosts;

}
          


Moreover, eBox framework allows to expose the methods you require by overriding a EBox::Model::ModelProvider protected method called _exposedMethods which is intended to help to develop this tedious part. With a simple method definition, you can expose some methods. Currently, you can add, delete, query and edit a single row. It is still not possible to apply these actions to a multiple rows.

We want to display four methods as the following list:

addVHost(name, enabled)

Add a new virtual host

removeVHost(name)

Remove an existing virtual host

updateVHost(name, [name|enabled]

Update an existing virtual host configuration

vHost(name)

Get virtual host configuration

There are used to simplify the virtual host management using name field as index. The definition for the above method signature is described in Example 8.45.

Example 8.45. _exposedMethods method

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

    my %exposedMethods =
      (
       'addVHost'    => { action  => 'add',
                          path    => [ 'VHostTable' ],
                        },
       'removeVHost' => { action  => 'del',
                          path    => [ 'VHostTable' ],
                          indexes => [ 'name' ],
                        },
       'updateVHost' => { action  => 'set',
                          path    => [ 'VHostTable' ],
                          indexes => [ 'name' ],
                        },
       'vHost'       => { action  => 'get',
                          path    => [ 'VHostTable' ],
                          indexes => [ 'name' ],
                        },
       );

    return \%exposedMethods;
}
          


It is quite self-describing. The exposed methods are stored in a named list acting the method name as key. As value it contains different parameters which are explained below:

action

The action to be performed by the method. Currently, there are four possible actions to be affected only by one row: 'add', 'del', 'set' or 'get'.

path

The path to access to the desired row. The first element of the array must be the model name. If the row is in a submodel realm, the way to get it is setting up the name of the field in the model which acts as a key to get the model. That is, the field name for the HasMany type within the selected model.

indexes

The index field name to access to the model, it must be the same number of elements in indexes and path parameters. If there is no unique field which is a candidate key to set as an index, you must set id value to access the model by identifier or position in the model content.

selector

This parameter is optional and only applicable to 'set' and 'get' actions. Both cases are used to discriminate those fields which the action will be performed. If there are a single element, the framework allows to update its value without saying which field element you are modifying. In 'get' case, the returned value is not a row with a single type instance but the self type instance from the field element.

In order to clarify the usage of exposed methods, the Example 8.46 describes a simple usage of these methods within a simple perl script.

Example 8.46. _exposedMethods usage by a Perl script

my $webMod = EBox::Global->modInstance('webserver');

# Adding devendra.banhart.com virtual host as valid one
$webMod->addVHost( name => 'devendra.banhart.com',
                   enabled => 1);

$webMod->isVHostEnabled( 'devendra.banhart.com'); # Returns true

$webMod->updateVHost( 'devendra.banhart.com', enabled => 0);
# Store the vhost configuration row (name, enabled)

my $vHostRow = $webMod->vHost( 'devendra.banhart.com' );
$vHostRow->{plainValueHash}->{enabled} # Return false
$vHostRow->{plainValueHash}->{name} # Return 'devendra.banhart.com'

# Deleting the virtual host
$webMod->removeVHost( 'devendra.banhart.com' );

          


As we see, the Web module API is pretty enhanced to support model management easily and human-readable way, making eBox framework powerful and completely scriptable.

8.2.6. Conclusion

As we have seen, eBox framework has been improved constantly and the developer burden is too much lower than previously to have an eBox module up and running. This makes eBox platform an easy customisable, extensive and stable platform to manage your own services and needs.