[Tutorial] Charm development (Beginner, part 2)

Tutorial: Charm development (Beginner, part 2)

Difficulty: Beginner Author: Erik Lönroth

What you will learn:

Building on the previous tutorial: Tutorial: charm development, Beginner: Part 1. In this part, you will:

  • Learn about the “layer-index”.
  • Learn how to add a ‘mysql’ database interface to charms.
  • Write a charm that writes a text-file using a template.
  • Deploy the example charm and relate to a mysql charm.
  • Use ‘juju ssh’ to login to the juju unit and display the contents of the text-file.

The ‘layer-index’

Lets examine the includes tag in layer.yaml

  - 'layer:basic'
  - 'layer:apt'

You learned from the first part of the tutorial that the ‘includes’ tag in ‘layer.yaml’ is similar to an “include” statement in python. All charms must include the ‘layer:base’.

The build system will add in code from the ‘include’ tag with our own.

Alot of already existing juju functionality can be found in the layer-index. Take a look and see if you find some interesting features you like to explore later.

As you see, we have also already added the layer:apt.

Since we can include both ‘layers’ and ‘interfaces’ lets see how that is done.

Add the interface:mysql

To add the mysql interface, we have to:

  • Modify ‘layer.yaml’
  • Modify ‘metadata.yaml’
  • Create a template inside a templates directory.

Lets do that.

Modify the ~/charms/layers/layer-example/layer.yaml to look like this:

  - 'layer:basic'
  - 'layer:apt'
  - 'interface:mysql'
     - hello 

Modify the ~/charms/layers/layer-example/metadata.yaml to look like this:

name: example
summary: A very basic example charm
maintainer: Your Name <your.name@mail.com>
description: |
  This is a charm I built as part of my beginner charming tutorial.
  - misc
  - tutorials
    interface: mysql

Create the templates directory:

The ‘templates’ directory is where templates go for charms. Lets create it.

cd ~/charms/layers/layer-example/
mkdir templates 

Modify the ~/charms/layers/layer-example/templates/text-file.tmpl to look like this:

DB_HOST = ‘{{ my_database.host() }}’ DB_NAME = ‘{{ my_database.database() }}’ DB_USER = ‘{{ my_database.user() }}’ DB_PASSWORD = ‘{{ my_database.password() }}’

The file above is in a “Jinja2” template format. Jinja2 templates allows us to perform very advanced template rendering with little effort.

Rewrite the layer_example.py


from charms.reactive import set_flag, when, when_not
from charmhelpers.core.hookenv import application_version_set, status_set
from charmhelpers.fetch import get_upstream_version
import subprocess as sp
from charmhelpers.core.templating import render

def install_example():

def set_message_hello():
    # Set the upstream version of hello for juju status.

    # Run hello and get the message
    message = sp.check_output('hello', stderr=sp.STDOUT)

    # Set the maintenance status with a message
    status_set('maintenance', message )

    # Signal that we know the version of hello

def write_text_file(mysql):
               'my_database': mysql,
    status_set('active', 'Ready: File rendered.')

def missing_mysql():
    status_set('blocked', 'Please add relation to MySQL')

def waiting_mysql(mysql):
    status_set('waiting', 'Waiting for MySQL')

What happens here is that:

  • We install the ‘hello’ package, just as we did in the first part of the tutorial.
  • The mysql interface raises a flag ‘database.available’ when the relation is joined, mysql has created a database, a username and password for us.
  • The database mysql object with the information we need is passed to us from the juju interface.
  • We pass on the mysql object to Jinja2 render() that renders the template.
  • We finish our work by setting the ‘active’ status.

What is the difference from tutorial part 1?

Look at the code in the function set_message_hello(). In the previous part of this tutorial, we set the ‘active’ status when the hello package was installed which was all we wanted our charm to do at that point.

However, in this part of the tutorial, we set status to ‘maintenance’ instead so we can do more work without signaling that we are ‘active’.

    # Set the maintenance status with a message
    status_set('maintenance', message )

Status is set to ‘active’ in the function write_text_file(mysql) when the template is rendered which is the goal for this charm.

def write_text_file(mysql):
               'my_database': mysql,
        status_set('active', 'Ready: File rendered.')

We are ready to rebuild.

Proof, Build, Deploy, Relate

We are done and can run through the build, deployment and try relate our charm to the mysql charm.

cd ~/charms/layers
charm proof layer-example
charm build layer-example
juju deploy ../trusty/example
juju deploy cs:mysql
juju relate example mysql

Note: We deploy our local charm by referencing with a path (…/trusty/example). mysql is on the other hand is deployed from the Charm Store (cs:mysql). “cs:mysql” tells juju to download the charm from over the network instead of uploading it from our local directory repository.

Look at the file with ‘juju ssh’

Lets look at the file we created inside the example/0 unit. We will do that using “juju ssh”.

juju ssh example/0 sudo cat /root/text-file.txt

juju ssh executes a command on the remote machine and if everything goes well, you should see the contents of the rendered file, something like below:

DB_HOST = ‘’ DB_NAME = ‘example’ DB_USER = ‘Dae7EGh9Zei0nee’ DB_PASSWORD = ‘aiRei1siePhewah’

Congratulations, you have completed the second part of the tutorial!

Next lesson:

In the next tutorial you will learn how to publish your charm to the charmstore so you can use it from anywhere around the world, in any cloud!

Move on to the next part of the tutorial series in Part 3.


Finished up this tutorial, great! The time after adding relation to the example unit changing status to ‘Ready: File rendered.’ was much longer than I was expecting, for some reason.

EDIT: The link to Part 3 at the end of the tut here is dead.
EDIT2: When I got to Proof, Build, Deploy, Relate I found mysql refusing to deploy as it’d defaulted series to ‘Zesty’ and Juju said ‘no matching agent binaries available’ - this may be due to me running Juju from the beta channel however. I got around this with “juju deploy cs:mysql --series bionic --force”.

Great input!

The dead link, I’ll fix now.

What do you suggest we do about the mysql part?

Personally, I would consider change the whole setup to use postgresql, but it reaches perhaps more people staying with mysql? Selecting tools that many can relate to, lowers the entry barrier.

There’s three options I see here being:

  1. Ask the MySQL charm author to update their charm to include bionic series - they’ll probably want to do that at some point before 2.5 goes stable anyway
  2. Fork it and reference the forked version in the tutorial - makes for a longer more confusing command line
  3. Switch to Postgres - as you say, lacks some familiarity from the Mysqllers…

I’d say either 1 or 3 are the most preferable…

1 Like

@seffyroff 1 would be the best fix then.

I could try figure out who those charmers are tomorrow. But its 00:00 in Sweden, so I have to call it a night.

1 Like


Looks like @marcoceppi is the author of the MySQL charm.

@erik-lonroth Sir the hooks part is missing in this tutorial series. Hooks are important in charm writing and whole charm’s relations are defined in hooks.
i would like to suggest that do make a tutorial on hooks and complete this series. there are many other things that need to be addressed.
hope you’ll look forward to it and will give positive response.
Thanking in advance.


1 Like

I might need some help on that, but I think you are right.

I got bit by the Zesty thing too (very annoying). I then just did:

juju deploy mysql --series=xenial

No need for --force.

1 Like

should be layer:basic here

1 Like

hi @erik-lonroth
thanks for this great tutorial.
About the mysql part, it will be better if you can include a few details, e.g.:

  1. what flags I can use in the @when_xxx decorator, how do I know
  2. why does some functions like write_text_file and wating_mysql take a mysql argument but others don’t
  3. what is the mysql arg, and how do I know what method it has

I figured out that the answer should be found from mysql interface repo: https://github.com/johnsca/juju-relation-mysql, but as a tutorial, these import info are worthy to mention, at least I was very confused at first.

BTW: I notice in the above repo README, it uses method like:

 mysql = endpoint_from_flag('db.available')

to get the mysql object, instead of an arg, which make it more consistent with other functions. Maybe it’s worthy to update this tutorial to match.

1 Like

Thanx for the input. I will try find some time to update it. I’m however currently a bit loaded with other juju related issues which will take a few days to get done.

Personally, I would even like to go for a postgresql example since the mysql charm has previously gone stale. (Might have been resolved by now?)

As practice, you could study this charm microsample | Juju that I’m about to write a tutorial around when I find the time. It combines a few concept around <flask, snap, charms> in a lightweight manner with a real world scenario using haproxy. @lasse

1 Like