[Tutorial] Charms with layer-snap (Intermediate)


#1

Tutorial: Charms with layer-snap

Difficulty: Intermediate
Author: Erik Lönroth

All code for the charm can be found here: https://github.com/erik78se/layer-pyapp-snapped

Preparations

  1. You should have completed the first beginner tutorial Part 1, Part 2, Part 3 before taking this tutorial.
  2. Use juju 2.4 or greater.
  3. charm-tools 2.4.4 or above or builds will fail.

Its good if you know how to create your own snap, but its not needed. You can learn snap:ing here.

What you will learn

  • Understand why snaps are useful in a Juju context.
  • Create a simple juju charm with “layer-snap

Why juju charms + snap ?

Snap packages are universal to Linux. This means that we can deploy our snapped applications without having to re-package for every linux distribution our users prefer, like ubuntu, debian, raspbian, NI Linux Real-Time distribution or centos. Great news for every DevOps team!

A specifically interesting use-case of snaps are for “IoT applications”. The reasons for that are tied to the properties of snaps:

  1. snap do atomic upgrades.
  2. snap are Linux distribution agnostic.
  3. snap has a very secure confinement.
  4. snap can be made very independent of its execution environment.

Using Juju+snap for development and deployment (CI/CD) will get your applications ready in no-time!

The “pyapp” snap

We will use a training snap I have created for us: pyapp. The pyapp application is a python3 application that writes a simple message to stdout and loggs a few messages to syslog.

The code for pyapp is available here: snap-pyapp

We can easily install and test pyapp directly from the http://snapstore.io:

sudo snap install pyapp --devmode
pyapp.run

Create a new charm & include “layer-snap”.

I called my charm “pyapp-snapped” Feel free to use a cooler name for yours if you like.

snapcaft create layer-pyapp-snapped

Hint! Recall from earlier tutorials that the “layer-” prefix in the charm create is to indicate this is a reactive charm?

Create your layer.yaml and modify the repo value as needed.

Pay some attention to that we are in fact installing two snaps here. “core” and “snap”. The reason for pulling in the “core” snap is that there seems to be a bug that occasionally don’t install the needed “core” snap. I think this bug might go away in the future. For now, see it as a bonus.

includes:
  - 'layer:basic'
  - 'layer:snap'
options:
  snap:
    core:
      channel: stable
      devmode: false
      jailmode: false
      dangerous: false
      classic: false
      revision: null
    pyapp:
      channel: edge
      devmode: true
      jailmode: false
      dangerous: false
      classic: false
      revision: null
repo: 'https://github.com/erik78se/layer-pyapp-snapped'

Create your metadata.yaml

name: pyapp-snapped
display-name: pyapp-snapped
summary: Simple charm for the pyapp snap
maintainer: Erik Lonroth <erik.lonroth@gmail.com>
description: |
  This charm deploys the pyapp  application, either from snapstore or if you supply it as a juju resource.
tags:
  - example
  - snap
  - pyapp
series:
  - bionic
resources:
  pyapp-snap:
    type: file
    filename: pyapp.snap
    description: A pyapp snap

Notice the included charm-resource “pyapp-snap”. This allow us to attach a snap package along with our deployment. If we do so, it will be uploaded to the juju controller and distributed to pyapp.

We won’t attach a local snap in this tutorial, but feel free to try it on your own (Read: layer-snap). The resource is just a placeholder and since we leave it out, layer-snap will automatically download our snap from snapstore.io when we deploy the charm.

Lets continue and see how easy the charm code becomes.

Create the reactive/layer_pyapp_snapped.py

from charmhelpers.core.hookenv import (
    open_port,
    status_set,
)
from charmhelpers.core.hookenv import application_version_set
from charms.reactive import (
    when,when_not,
    set_flag,clear_flag
)
from charms.layer import snap

@when('snap.installed.pyapp')
def set_pyapp_snapped_available():
    """
    When snap is installed, just keep on updating.
    """
    version = snap.get_installed_version('pyapp')
    channel = snap.get_installed_channel('pyapp')
    application_version_set(version)
    status_set('active', "Ready pyapp-{} ({})".format(version,channel))
    set_flag('my.pyapp.application.available')

@when_not('snap.installed.pyapp')
def pyapp_not_installed():
    """
    Whenever the snap is not installed, clear statuses and flags.
    """
    application_version_set("")
    clear_flag('my.pyapp.application.available')
    status_set('waiting', "Pyapp snap not installed.")

Proof, Build and Deploy

charm proof
charm build

Deploy from our local build.

juju deploy /home/erik/charms/builds/pyapp-snapped
Deploying charm "local:bionic/pyapp-snapped-0".

Test it

Lets see if the snap was installed and works.

juju ssh pyapp-snapped/0 /snap/bin/pyapp.run

Great! You have just created a juju charm that deploys a snap!

Next lesson

Next we will learn to add in a juju-action and relate our charm to a logging and search facility - the ELK (Elasticsearch, Logstash, Kibana) stack. NOT YET WRITTEN.

Contributors

@jamesbeedy - For teaching me about juju
@stub - Author of the layer-snap
@timClicks - for peer-reviewing the text


#2

@stub - I saw you merged my PR for the new functions. Will you update layer-index aswell for people building with charm pull ? I’m also getting a bit confused now over which repo (Launchpad vs Github) is the one I should use…


#3

Thanks for adding this @erik-lonroth. One small nit: capitalisation.

My understanding:

  • Juju (rather than juju, JUJU)
  • snap and snaps (rather than SNAP and SNAPs)

#4

@timClicks Thanx! I’ve gone through with your recommendations. It made sense.


#5

Another great looking tut @erik-lonroth! I’ll step through this in the next few days, thanks!