| Version 1 (modified by mburillo@…, 17 months ago) (diff) |
|---|
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::Validate qw(:all);
sub new
{
my $class = shift;
my %parms = @_;
my $self = $class->SUPER::new(@_);
bless($self, $class);
return $self;
}
sub _table
{
my @fields =
(
# Here you should add your instances of subclass of EBox::Types::Abstact to
# define the form's fields. For example to add a text field:
# new EBox::Types::Text(
# 'fieldName' => 'ExampleField',
# 'printableName' => __('Example text field'),
# 'size' => '8',
# 'unique' => 1,
# 'editable' => 1,
# 'help' => __('This field is an example.'),
# ),
);
my $dataTable =
{
'tableName' => 'Settings',
'printableTableName' => __('Settings'),
'printableRowName' => __('Settings'),
'modelDomain' => 'Apache2',
'defaultActions' => ['add', 'del', 'editField', 'changeView' ],
'tableDescription' => \@fields,
'help' => '', # FIXME
};
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 type at the moment, so we will get rid of it. Instead, we'll use a Port type.
- As we only have one field, we will remove Field 2, and we will change the name of Field 1 to Listening Port, 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 EBox::Gettext;
use EBox::Validate qw(:all);
use EBox::Types::Port;
use strict;
use warnings;
use base 'EBox::Model::DataForm';
sub new
{
my $class = shift;
my %parms = @_;
my $self = $class->SUPER::new(@_);
bless($self, $class);
return $self;
}
sub _table
{
my @fields =
(
new EBox::Types::Port(
'fieldName' => 'listeningPort',
'printableName' => __('Listening port'),
'defaultValue' => 80,
'editable' => 1
)
);
my $dataTable =
{
'tableName' => 'Settings',
'printableTableName' => __('Settings'),
'modelDomain' => 'Apache2',
'defaultActions' => ['add', 'del', 'editField', 'changeView' ],
'tableDescription' => \@fields,
'help' => '', # FIXME
};
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_0.1_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:
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
Fetching the current value
We already have a web interface with a simple form where the user can set the Apache port. Let's make a simple script that will show how to fetch this value. The goal of this script is to show you how we can retrieve values from a data model.
Enable the Apache2 module, change the default port and save changes.
You can save this code into a file and run it with sudo perl filename:
use strict;
use warnings;
use EBox;
use EBox::Model::ModelManager;
# This is the very first thing we always have to do from external scripts
EBox::init();
# Instance ModelManager
my $manager = EBox::Model::ModelManager->instance();
# Gently ask for the model called apache2/Settings
my $settings = $manager->model('apache2/Settings');
# Print the value stored in listeningPort
print $settings->row()->valueByName('listeningPort') . "\n";
# We could also use autoload magic to do the same thing as above.
print $settings->listeningPortValue() . "\n";
Models must always be requested through an instance of EBox::Model::ModelManager. Note that to retrieve the model we use:
my $settings = $manager->model('apache2/Settings');
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. To do that we run:
$settings->row()->valueByName('listeningPort')
$settings is an instance of our model. The method row() returns an instance of EBox::Model::Row which we will see in depth later. For now, it is enough to know that it's a class that will allow us to access all of the fields in our model. valueByName() is a method to get the value of the given field.
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:
# Method: _writeConfiguration
#
# This method uses a mason template to generate and write the configuration for
# /etc/apache2/ports.conf
#
sub _writeConfiguration
{
my ($self) = @_;
my $mgr = EBox::Model::ModelManager->instance();
my $port = $mgr->model('apache2/Settings')->row()->valueByName('listeningPort');
$self->writeConfFile('/etc/apache2/ports.conf',
'/apache2/service.conf.mas',
[ port => $port ]);
}
# Method: _setConf
#
# Overrides: <EBox::Module::Service::_setConf>
#
sub _setConf
{
my ($self) = @_;
$self->_writeConfiguration();
}
We have already seen how to fetch the value from our data model, so 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
{
return [
{
'name' => 'apache2',
'type' => 'init.d',
'pidfiles' => ['/var/run/apache2.pid']
}
];
}
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, _writeConfiguration(). 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.
Attachments
-
just-installed.png
(25.9 KB) -
added by mburillo@… 17 months ago.