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:
Enable and disable the service as other eBox modules do
Some general configuration settings as port number to listen to
HTTP requests or enable Web pages provided by users' home in public_html
subdirectory.
Virtual host creation and configuration giving automatic name resolution management using our DNS module.
These steps are going to follow to develop this module:
Define the data model to accomplish the requested features
Define the graphical user interface using Composites
Create the new module from a template publishing the data model and composites
Common tasks done as well in the long way Section such as generate configuration files, control daemon execution, firewall rules and set menu and status summary.
Expose the desired API to other eBox's modules, Perl script clients or SOAP interface
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.
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.
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.
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".
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.
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:
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:
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'); }
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.
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 -> .
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.
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:
Add a new virtual host
Remove an existing virtual host
Update an existing virtual host configuration
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:
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'.
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.
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.
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.