Writing a layer by example
In this document, we will be writing a charm layer for Vanilla. Vanilla is an open source, themeable, pluggable, and multi-lingual forum software, which enables communities to host a forum for discussion topics at scale. Powered by PHP and MySQL, Vanilla is a fine example of a three-tiered application:
- Database (MySQL)
- Middleware (PHP App)
- Load Balancing via HTTP interface
Prepare your workspace
Building off of $JUJU_REPOSITORY,
we want to add two more environment variables to your session. We recommend
adding these into your shell configuration file so that they are always
available.
LAYER_PATH
First off, you require a local charm repository in
which to work. This involves creating three directories -- layers,
interfaces, and charms -- and setting some environment variables.
The layers directory contains the source code of the layered charm covered in
our examples. The interfaces directory is where you'd place any
interface-layers you may wish to write, and the
charms directory holds the assembled, ready to deploy charm.
export JUJU_REPOSITORY=$HOME/charms
export LAYER_PATH=$JUJU_REPOSITORY/layers
export INTERFACE_PATH=$JUJU_REPOSITORY/interfaces
mkdir -p $LAYER_PATH $LAYER_PATH/vanilla
cd $LAYER_PATH/vanilla
Note: Exporting the environment variables in this way only sets the variables for the current terminal. If you wish to make these changes persist, add the same export statements to a resource file that are evaluated when you create a new console such as ~/.bashrc depending on your shell.
Determine a base layer and additional relations
To best leverage existing work, it is important to choose the right base layer for your charm. The available layers and interfaces can be found at interfaces.juju.solutions.
For our example, Vanilla is a PHP5 application, so the layer we will use is the
apache-php5 layer. This is a "runtime"
layer that provides a common basis for specific applications to be built on top
of. Out of the box, this will give us Apache2 running on a configurable port,
mod-php5, and support for the http
interface
for running behind a proxy for high availability and scaling. On top of that,
we'll use the mysql
interface to facilitate our
database interface. We will put these in a layer.yaml file, which tells the
builder what layers and interfaces to combine with yours to create the finished
charm:
includes: ['layer:apache-php', 'interface:mysql']
Fill in your charm layer
With that decided, we're ready to start creating our charm layer. The README,
icon, and copyright file will be the same. The metadata.yaml will be the same
except that the website relation will be managed for us, so we can leave out
the provides section:
name: vanilla
summary: Vanilla is an open-source, pluggable, themeable, multi-lingual forum.
maintainer: Your Name <your@email.tld>
description: |
Vanilla is designed to deploy and grow small communities to scale.
This charm deploys Vanilla Forums as outlined by the Vanilla Forums installation guide.
tags:
- social
requires:
database:
interface: mysql
The apache-php layer requires some additional configuration to tell it how to
install and configure your application. This goes in an apache.yaml file:
packages:
- 'php5-mysql'
- 'php5-gd'
sites:
vanilla:
options: 'Indexes FollowSymLinks MultiViews'
install_from:
source: https://github.com/vanillaforums/Garden/archive/Vanilla_2.0.18.8.tar.gz#sha256=acf61a7ffca9359c1e1d721777182e51637be59744925935291801ccc8e8fd55
This tells the apache-php layer to install some additional packages for PHP,
where to fetch your application from (as well as its cryptographic hash to
verify the payload), and a few options for the vhost entry. The application will
be installed under /var/www/{site_name} where {site_name} is the name of the
block under sites (in this case vanilla).
Implementing your layer
The apache-php layer and mysql interface use
charms.reactive to coordinate with
your layer. This makes it easy to coordinate the state of multiple layers,
relations, configuration options, etc. Code for reactive handlers should live
under a reactive/ directory in your charm layer. In our case, we'll use
reactive/vanilla.py. The apache-php layer sets an apache.available state when
your application is done being installed, and the mysql interface sets a similar
{relation_name}.available state (where {relation_name} depends on what you
called the relation in your metadata.yaml) to indicate that MySQL has provided
you with a complete set of database credentials. Thus, by watching for both of
those states, you can easily tell when the right time to set up your application
is:
@when('apache.available', 'database.available')
def setup_vanilla(mysql):
pass
You'll notice that the MySQL relation is passed in so that you can easily access
the database connection information. Since the Apache installation and MySQL
relation negotiation are all handled for us, the only thing left to do is create
the conf/config.php file for Vanilla. To make things easy, we will render a
template to populate the values. We'll also handle a few more state changes to
provide better status reporting and react to our database going away in an
intelligent manner:
import pwd
import os
from charmhelpers.core.hookenv import status_set
from charmhelpers.core.templating import render
from charms.reactive import when, when_not
from charms.reactive import set_state, remove_state
@when('apache.available', 'database.available')
def setup_vanilla(mysql):
render(source='vanilla_config.php',
target='/var/www/vanilla/conf/config.php',
owner='www-data',
perms=0o775,
context={
'db': mysql,
})
uid = pwd.getpwnam('www-data').pw_uid
os.chown('/var/www/vanilla/cache', uid, -1)
os.chown('/var/www/vanilla/uploads', uid, -1)
set_state('apache.start')
status_set('maintenance', 'Starting Apache')
@when('apache.available')
@when_not('database.connected')
def missing_mysql():
remove_state('apache.start')
status_set('blocked', 'Please add relation to MySQL')
@when('database.connected')
@when_not('database.available')
def waiting_mysql(mysql):
remove_state('apache.start')
status_set('waiting', 'Waiting for MySQL')
@when('apache.started')
def started():
status_set('active', 'Ready')
By setting the apache.start state, we are letting the apache-php layer know that we have finished configuring the application and it is ready to start.
The templates/vanilla_config.php file is straightforward:
<?php if (!defined('APPLICATION')) exit();
$Configuration['Database']['Host'] = '{{ db.host() }}';
$Configuration['Database']['Name'] = '{{ db.database() }}';
$Configuration['Database']['User'] = '{{ db.user() }}';
$Configuration['Database']['Password'] = '{{ db.password() }}';
?>