Overview

Lucky is a framework for writing Juju charms. It is being designed specifically to support writing Docker-powered charms easily, but its use is not limited to Docker. We want Lucky to be as easy to use as possible and be very well documented. We will focus on putting the developer's experience first, starting small and adding features as they become necessary or useful.

Getting Started

See the Getting Started guide to learn how to write your first Lucky charm. The guide will walk you through creating a charm for CodiMD.

Development

Lucky is currently in Alpha. There are likely bugs and the documentation is still a work-in-progress, but you should still be able to use it to make charms.

Bugs, Features, and Questions

If you have any bug reports or feature requests you can create a Taiga issue and we'll see what we can do to help.

For questions or general discussion there is also a Lucky category on the Juju forum.

Getting Started

Welcome to the getting started guide for the Lucky charming framework. Here we will walk through the process of creating a charm for CodiMD. This charm will provide an http interface, allowing you to hook it up to other charms such as HAProxy for load balancing. The charm will also require a pgsql interface which we will use to connect the charm to a PostgreSQL database charm.

You can find the entire source for the charm we will be writing in this tutorial here.

Installing Required Tools

Before we get started, we are going to need some tools. Juju development is usually done on Linux and this guide will assume that you are working in a Linux environment. While it is possible to develop charms on Windows, if you already have a Juju cluster, it currently isn't the easiest way to get started.

If your workstation is a Windows machine, the easiest way to develop charms is in a Linux Vagrant machine. It is outside of the scope of this tutorial to detail how to setup a Linux Vagrant machine.

Note: If already have a Juju cluster and you have the Juju CLI setup on Windows, you should be able to develop charms on Windows without a VM, but you might require more setup in order to install the charm tools. Even if you don't install the charm tools, you can still develop your charm, you just won't be able to push it to the store.

This is not very tested yet. If you need help getting setup you can open a forum topic.

Juju

The first step is to setup a Juju development cluster. See the Juju getting started guide for more information.

Charm Tools

The charm tools CLI is what we will use to push our charm to the charm store. It can be installed with its snap:

sudo snap install charm --classic

You can skip installing the charm tools if you don't want to push charms to the store.

Lucky

Now we install Lucky itself. You can download Lucky from the GitHub releases page. Lucky will eventually support at least the Snap package manager, but for now you can also use this one-liner to install Lucky:

curl -L https://github.com/katharostech/lucky/releases/download/v0.1.0-alpha.1/lucky-linux-x86_64.tgz | sudo tar -xzC /usr/local/bin/

You can verify that the install worked:

$ lucky --version
lucky v0.1.0-alpha.0

Creating Your Charm

Now that we have the required tools, we can create our charm. Lucky comes with a built-in charm template that you can use:

$ lucky charm create codimd
Display name [codimd]: Codimd
Charm name [codimd]: 
Charm summary [A short summary of my app.]: A realtime collaborative notes platform.
Charm maintainer [John Doe <johndoe@emailprovider.com>]: My Name <myname@myprovider.com>

This will create a new dir named codimd with the metadata that you filled in and some example code.

Configuring Charm Metadata

Lets first take a look at that metadata:

metadata.yaml

name: codimd
display-name: Codimd
summary: A realtime collaborative notes platform.
maintainer: My Name <myname@myprovider.com>
description: |
  A realtime collaborative notes platform.
tags:
  # Replace "misc" with one or more whitelisted tags from this list:
  # https://jujucharms.com/docs/stable/authors-charm-metadata
  - misc
subordinate: false
provides:
  provides-relation:
    interface: interface-name
requires:
  requires-relation:
    interface: interface-name
peers:
  peer-relation:
    interface: interface-name

That pretty much has what we need, but we will want to change those fake relations to the ones that we actually need. Go ahead and remove the provides, requires, and peers sections and replace them with this:

provides:
  website:
    interface: http
requires:
  database:
    interface: pgsql

With this we tell Juju that:

  • we have a website relation that we provide and the way we interact with that relation conforms to the http interface.
  • we have a database relation that we require and the way we interact with that relation conforms to the pgsql interface.

Interfaces are names for the way in which a charm will communicate over a relation. Only relations with the same interface will be allowed to be connected to each-other. This means there is no way to accidentally connect a requires pgsql relation to a charm that only provides redis.

You can find documentation for some interfaces in the Juju interfaces docs.

Next we'll look at our config.

config.yaml

The template config yaml looks like this:

# These are config values that users can set from the GUI or the commandline
options:
  name:
    type: string
    default: John
    description: The name of something
  enable-something:
    type: boolean
    default: False
    description: Whether or not to enable something
  count:
    type: int
    default: 100
    description: How much of something

The purpose of config.yaml is to define the options to our charm that users are allowed to change. We can see all of the available config options for CodiMD in their documentation. For now we'll just give some of the minimal essential options in our config.yaml. Also, note that we don't need to put the database connection info in the config because we will use Juju's relations to automatically configure the database connection.

options:
  domain:
    type: string
    default: example.org
    description: The domain CodiMD is hosted under
  url-path:
    type: string
    default: ""
    description: If CodiMD is run from a subdirectory like "www.example.com/<urlpath>"
  port:
    type: int
    default: 9000
    description: The port to host CodiMD on
  https:
    type: boolean
    default: false
    description: Whether or not the server will be accessed over HTTPS

That config will give us enough information for us to get started, but if we wanted to make a general-purpose charm for the community we would want to add the rest of the configuration from the documentation.

lucky.yaml

The final metadata file we are interested in is the lucky.yaml file. This file acts as your "control panel" so to speak when it comes to the execution of your charm logic. The job of the lucky.yaml is to tell lucky when and how to execute your charm scripts. The charm template comes with an example lucky.yaml that shows you everything that you can put in a lucky.yaml file. For the sake of this tutorial we are going to take everything out of the lucky.yaml and build on it as we go.

lucky.yaml:

# Nothing here yet!

Writing Your First Script

Now we are ready to write our first script! In Lucky there are two kinds of scripts, host scripts and container scripts, which are put in the host_scripts/ and container_scripts/ directories. The difference is that host scripts run on the host and container scripts are mounted and run inside of your containers. Our scripts for CodiMD will go in the host_scripts/ dir.

You will notice that there are some scripts from the charm template already in the hosts_scripts/ and container_scripts/ dirs. These are just examples and you can remove them for this tutorial.

The first script that we will create for our charm is the install script. Note that the name of the script is arbitrary and you could call it whatever you want.

install.sh:

#!/bin/bash

# Exit script early if any command in the script fails
set -e

# Set the status for this script so users can se what our charm is doing
lucky set-status maintenance "Starting CodiMD"

# Set the Docker image, this will cause lucky to create a container when this
# script exits
lucky container image set quay.io/codimd/server:1.6.0-alpine

# Set a named status that can be changed from other scripts.
# Here we notify the user that we need a database relation before CodiMD will work
lucky set-status --name db-state blocked "Waiting for database connection" 

# Clear the status for this script by setting the status to active without a message.
# This makes sure that our "Starting CodiMD" message goes away.
lucky set-status active

First notice that we have a "shabang", as it is called, at the top of the file: #!/bin/bash. This tells the system to execute our file with bash. We also include a set -e, which will make sure that he script will exit early if any of the commands in it fail. Additionally we need to make our file executable by running chmod +x install.sh. This makes sure that Lucky will be able to execute the script when the charm runs.

After that we use the lucky set-status command set the Juju status, which will be visible in the Juju GUI.

Then we set the Docker container image with the lucky container image set command. Setting a container's image is the way to create a new contianer that will be deployed by Lucky automatically when our script exits. Additionally, when we change any container configuration, such as environment variables or port bindings, Lucky will wait until our script exits and then apply any changes that we have made. We will see more of how this works later.

Understanding Lucky Status

At this point the Lucky status mechanism should be explained. In Lucky, when you call set-status, by default, Lucky will set the given status for only that script. This means that if another script uses set-status it will not overwrite the status that the previous script set, but will instead add its status to the previous status by comma separating the list of all script status messages.

It is common pattern in Lucky scripts to have a lucky set status maintenance "Message" at the top of the script and a lucky set-status active at the bottom. This makes sure that the user will be notified of the script's action, and that the action message will be cleared before the script exits.

Alternatively, when you set a status with a --name <name>, you can set that status from any script by specifying the same --name. In this exmple, we use a status with a db-state name that we use to indicate the status of our database connection. When the charm is first installed, it will not have a database relation, and we use this opportunity to tell the user that we need a database connection to work. Later, when we get a database connection in a different script, we will call lucky set-status --name db-state active to clear the blocked status.

Adding Our Script to the lucky.yaml

Ok, so we now have a written script, but currently there is nothing instructing Lucky to run the script at any time. The script existing is not enough to cause it to run. That is why we add entries to the lucky.yaml, to tell Lucky when to run our scripts.

In this case, we want our install.sh host script to run when the Juju install hook is triggered:

lucky.yaml:

# Juju hooks
hooks:
  
  # When the charm is installed
  install:
    # Run our install.sh host script
    - host-script: install.sh

Pretty simple right? Now Lucky will run our install.sh host script whenever the Juju install hook is run.

Lets move on to the configure.sh script.

Writing the configure.sh Script

So we have our app installing, and actually starting ( becuse Lucky will start the container when we set the docker image ) with the install.sh script, but it won't really do anything because it doesn't have any of our configuration. That is what we are going to do with our configure.sh script. We are going to read the configuration values that we have defined in our config.yaml and use those values to set environment variables on our CodiMD container.

configure.sh:

#!/bin/bash

set -e

lucky set-status maintenance "Configuring CodiMD"

# Get the config values and put them in shell variables
domain="$(lucky get-config domain)"
url_path="$(lucky get-config url-path)"
https="$(lucky get-config https)"
add_port="$(lucky get-config add-port-to-domain)"
port="$(lucky get-config port)"

# Set the respective container environment variables
lucky container env set \
    "CMD_DOMAIN=$domain" \
    "CMD_URL_PATH=$url_path" \
    "CMD_PORT=$port" \
    "CMD_PROTOCOL_USESSL=$https" \
    "CMD_URL_ADDPORT=false" # This last config fixes issues when hosting on different ports

# Remove any mounted container ports that might have been added in previous
# runs of `configure.sh`.
lucky container port remove --all

# Mount configured port on the host to configured port in the container
lucky container port add "$port:$port"

# Clear any previously opened firewall ports
lucky port close --all

# Open the configured port on the firewall
lucky port open $port

lucky set-status active

In this script we introduce some extra lucky commands. As always, you can access extra information on those commands in the Lucky client documentation.

Note: You can also access the CLI documentation from the Lucky CLI itself, by prefixing the command that we use in our script with client and adding the --doc flag. For example, you can run lucky client get-config --doc on your workstation to get terminal rendered view of the same CLI documentation available on this site. This can be very useful when needing to quickly look something up without using a web browser or the internet.

Overall this script is pretty simple, when the config changes, we make sure that our container environment variables are up-to-date. Also we make sure that we mount the configured port on the host to the container.

When working with ports that are opened according to configuration values, we need to make sure that we remove any ports that were opened by previous configuration. This makes sure that we don't end up with multiple ports mounted into the container if the user changes the configured port and the configure.sh script is re-run.

The Difference Between lucky container port and lucky port

You may notice in the above example that we do both a lucky container port add and a lucky port open, so what is the difference?

lucky contianer port add will add a port binding from the host to the container. lucky port open, on the other hand, registers that port with Juju so that it will be opened through the host's firewall when users run juju expose codimd.

If you want to be able to communicate to a port only on the private network, such as app-to-app communication, you do not want to use lucky open because that will expose that port to the internet on the host's firewall. In such a case you will still need to use lucky contianer port add to make sure that the containers can communicate.

If you do want to be able to hit the port from the internet, though, like in the case of CodiMD, you will need to lucky open port and the users will need to juju expose the application before you can access that port.

Adding configure.sh to the lucky.yaml

Now we can add configure.sh to the lucky.yaml just like we did with the install.sh script.

# Juju hooks
hooks:
  
  # When the charm is installed
  install:
    # Run our install.sh host script
    - host-script: install.sh
  
  # When configuration has been changed
  config-changed:
    # Run our configure.sh host script
    - host-script: configure.sh 

Handling the Database Relation

OK, so we now have our app and its user configuration. The next step is to setup the database relation. We will use the database relationship in Juju to automatically configure our database connection when the user runs juju relate codimd postgresql:db.

We are going to create a new script for handling the database relation:

handle-datbase-relation.sh:

#!/bin/bash

set -e

# Set the database name
db_name=codimd

# Here we match on the first argument ( $1 ) passed in from the lucky.yaml

if [ "$1" = "join" ]; then
    lucky set-status --name db-state maintenance "Connecting to database"

    # Set the name of the database that we want the server to create for us
    lucky relation set "database=$db_name"

elif [ "$1" = "update" ]; then
    lucky set-status --name db-state maintenance "Updating database connection"

    # Get the values from the connected database relation
    dbhost="$(lucky relation get host)"
    dbport="$(lucky relation get port)"
    dbuser="$(lucky relation get user)"
    dbpassword="$(lucky relation get password)"

    # If any of those values have not be set yet, exit early and wait until next update
    if [ "$dbhost" = "" -o "$dbport" = "" -o "$dbuser" = "" -o "$dbpassword" = "" ]; then
        exit 0
    fi

    # Set database connection environment variable
    lucky container env set "CMD_DB_URL=postgres://$dbuser:$dbpassword@$dbhost:$dbport/$db_name"

    lucky set-status --name db-state active

elif [ "$1" = "leave" ]; then
    lucky set-status --name db-state maintenance "Disconnecting from database"

    # Unset database connection environment variable
    lucky container env set "CMD_DB_URL="

    lucky set-status --name db-state blocked "Waiting for database connection"
fi

And here is the section we need in the lucky.yaml ( still under the hooks section ):

  # When we are related to a database
  database-relation-joined:
    # Run our database join handler
    - host-script: handle-database-relation.sh
      args: ["join"]

  # When the datbase relation changes
  database-relation-changed:
    - host-script: handle-database-relation.sh
      args: ["update"]
  
  # When we are disconnected from a database
  database-relation-departed:
    # Run our database departed handler
    - host-script: handle-database-relation.sh
      args: ["leave"]

This is a larger chunk of code to process so lets break it down a little:

Joining the Database Relation

To handle the database join relation we add this section to the lucky.yaml:

lucky.yaml:

  # When we are related to a database
  database-relation-joined:
    # Run our database join handler
    - host-script: handle-database-relation.sh
      args: ["join"]

We say that on the database-relation-joined hook we want to run the handle-database-relation.sh script and pass it join as its first argument. Inside of our handle-database-relation.sh script we then use an if statement to check whether the first argument ( $1 ) is join:

handle-database-relation.sh:

if [ "$1" = "join" ]; then
    lucky set-status --name db-state maintenance "Connecting to database"

    # Set the name of the database that we want the server to create for us
    lucky relation set "database=$db_name"

When the database relation is joined, we set our db-state status to "Connecting to database". Also, following the pgsql interface documentation, when we join a pgsql relation, it is the job of our charm to set the database key on the relation so the PostgreSQL charm knows what database to create for our application.

After we have set the database key on this relation, we will exit and wait until the next database-relation-changed hook is run at which point PostgreSQL will have set the database hostname, port, username, and password that we need to connect to it.

Note: Because our script is executing as a part of a relation hook, Lucky has extra context about which relation to set the database value for and we do not need to specify which because it will default to whatever relation triggered the run of the relation hook.

If you ever needed to lucky relation set or lucky relation get outside of a relation hook, then you will need to specify the relation id to set/get. You will see this later when we setup the http relation.

Updating the Database Relation

Now we need to handle any updates that happen to our established PostgreSQL relation:

lucky.yaml:

  # When the datbase relation changes
  database-relation-changed:
    - host-script: handle-database-relation.sh
      args: ["update"]

handle-database-relation.sh:

elif [ "$1" = "update" ]; then
    lucky set-status --name db-state maintenance "Updating database connection"

    # Get the values from the connected database relation
    dbhost="$(lucky relation get host)"
    dbport="$(lucky relation get port)"
    dbuser="$(lucky relation get user)"
    dbpassword="$(lucky relation get password)"

    # If any of those values have not be set yet, exit early and wait until next update
    if [ "$dbhost" = "" -o "$dbport" = "" -o "$dbuser" = "" -o "$dbpassword" = "" ]; then
        exit 0
    fi

    # Set database connection environment variable
    lucky container env set "CMD_DB_URL=postgres://$dbuser:$dbpassword@$dbhost:$dbport/$db_name"

    lucky set-status --name db-state active

When the database relation changes, we use lucky relation get to get the host, port, user, and password from the PostgreSQL relation. If any of those values are not set ( i.e. equal to "" ), then we exit 0 and wait until the next database-relation-changed hook until those values are set. Once all values are set, we set our CMD_DB_URL environment variable in the container. This will make CodiMD connect to our database.

Once that environment variable has been added to the container, Lucky will be sure stop, remove, and re-create the container with the new environment variable. At that point, we should have a functional CodiMD instance! Still, we've got some other code to write and we'll finish that off before we try to run our charm.

Leaving the Database Relation

Disconnecting from our database relation is simple:

lucky.yaml:

  # When we are disconnected from a database
  database-relation-departed:
    # Run our database departed handler
    - host-script: handle-database-relation.sh
      args: ["leave"]

handle-database-relation.sh:

elif [ "$1" = "leave" ]; then
    lucky set-status --name db-state maintenance "Disconnecting from database"

    # Unset database connection environment variable
    lucky container env set "CMD_DB_URL="

    lucky set-status --name db-state blocked "Waiting for database connection"
fi

Handling the Website Relation

Now we are ready to handle our website relation.

It is worth noting that our CodiMD charm could work perfectly fine without providing a website relation. We could just as well access our CodiMD instance over the configured port and use juju expose to open the firewall ports so that we can get to it. The reason that we provide a website relation with the http interface is so that the user could choose to deploy CodiMD behind a reverse proxy such as Haproxy or Katharos Technology's Let's Encrypt Proxy charm. Providing an http relation for CodiMD makes it compatible with the larger charming ecosystem and gives the user more deployment options.

Note: On the charm store you can view a list of charms that take http relations.

For our lucky.yaml we will need to add a new section for the website-relation-joined hook and, like our database handler, we use an argument to the script to indicate what kind of action to perform:

lucky.yaml:

  # When a new app is related to our website relation
  website-relation-joined:
    # Run our website join handler
    - host-script : handle-website-relation.sh
      args: ["join"]

Additionally we will need to add an extra script to our existing config-changed hook:

  config-changed:
    # Run our configure.sh host script
    - host-script: configure.sh 
    # Update our website relations with new config
    - host-script: handle-website-relation.sh
      args: ["update"]

This is what needs to go in our handle-website-relation.sh script:

handle-website-relation.sh:

#!/bin/bash

set -e

# Here we match on the first argument ( $1 ) passed in from the lucky.yaml

# If we are joining a new relation
if [ "$1" = "join" ]; then
    lucky set-status maintenance "Joining HTTP relation"

    # Set hostname and port values for the joined relation
    lucky relation set \
        "hostname=$(lucky private-address)" \
        "port=$(lucky get-config port)"

# If we are supposed to update our existing relations
elif [ "$1" = "update" ]; then
    lucky set-status maintenance "Updating HTTP relations"

    # For every appliacation connected to or "website" relation
    for relation_id in $(lucky relation list-ids --relation-name website); do
        # Set the hostname and port values for the relation
        lucky relation set --relation-id $relation_id \
            "hostname=$(lucky private-address)" \
            "port=$(lucky get-config port)"
    done
fi

lucky set-status active

As shown in the http interface documentation, it is our job as the provider of an http relation to set the hostname and port values on our relation. In this script, we do this when we join the relation.

When our charm configuration changes, we have also configure in the lucky.yaml to run this script with the update arg. The goal here is to go through all of our website relations and lucky relation set the hostname and port to make sure it is up-to-date without our new config.

Note that because the update section of our script is triggered by the config-changed hook and is not a part of a relation hook, we need to get a list of all of the relation ids for our website relation and loop through them. For each relation id, we run lucky relation set and pass in the relation id. This way, any related applications that needed to know our hostname and port will be updated when our port changes.

Testing Our Charm

Congratulations, you have finished your first Lucky charm! That sums up everything you need to make a decent CodiMD charm with Lucky. You can find the full source for the charm here. The last thing we need to do is test it. The beauty of the charm system is that, while it might be a little bit of work to write a charm, using the charm is super easy.

Building & Deploying

Lucky charms mus be built before they can be deployed. This is very easy:

lucky charm build

After that you will find the built charm in ./build/codimd. This directory is what you need to deploy with Juju:

juju deploy ./build/codimd

You will need to configure the domain and port for the codimd app before you can get to it. In this case the IP address is assumed to be the address of the server you depoyed CodiMD to. You will need to put the port in the domain as well if you are not hosting on port 80 or 443:

juju config codimd domain=10.176.159.221:3000 port=3000

After that settles our charm should show as blocked and "Waiting for database connection". To fix that we deploy the PostgreSQL charm and relate it to our codimd charm.

juju deploy postgresql
juju relate codimd postgresql:db

After PostgreSQL finishes deploying and configuring, you should be able to hit your new CodiMD instance on its IP and port. You're done!

Publishing Your Charm

OK, you were almost done, but now you want to share your charm creation with the world! To get a full guide on publishing charms to the store you can read the Juju Documentation, but we'll go over the quick version here.

First you make sure you have built you charm:

lucky charm build

Then we login to the charm store Juju's charm tools:

charm login

Next we push our charm to the charm store under our account:

$ charm push ./build/codimd codimd
cs:~username/codimd-0

Note: You cannot delete a charm from the store, once you have pushed it, without contacting the store administrators so be careful to get the charm name right the first time!

This will push our charm to the store and print out the newly created revision of the charm. Now we release our charm to the stable channel:

charm release cs:~username/codimd-0

And we grant read access to the charm to everyone:

charm grant cs:~username/codimd everyone

You should now be able to see the charm in the public charm store at: https://jaas.ai/u/username/codimd/0.

Subsequent pushes of the charm will have the number at the end of the charm name incremented and they will need to be released individually to the stable channel once you have tested them:

# Make changes to the charm
$ lucky charm build
$ charm push ./build/codimd codimd
cs:~username/codimd-1
$ charm release cs:~username/codimd-1

Wrapping Up

That's it for the tutorial. Very good job if you have made it this far! If you have any questions or you need help with something, do not hesitate to open a forum topic on the Juju forum. We would appreciate any feedback on how Lucky worked for you or feedback on the documentation and this guide. Thank you for trying out Lucky!

Lucky Development

These are guides for the development of the Lucky framework itself.

Design

The Lucky framework will be implemented in Rust and will consist of a daemon that runs as a part of the charm and a CLI that will be used by scripts to communicate to the daemon. The overall design can be seen in the diagram below.

charm-framework-diagram

Hooks

Just like every Juju charm, Lucky charms implement a number of different hooks that the Juju controller will execute. These hooks will not be implemented by the developer but will be provided by the Lucky charm template.

Install

The install hook will first download one of our automated builds of the Lucky framework, which will be a standalone Rust executable. The install hook will be sure to download the binary appropriate to the platform architecture.

After downloading the Lucky binary it will run the Lucky daemon.

Note: The Lucky binary also acts as the CLI that is used to communicate with the running daemon.

Other Hooks

All of the other hooks are scripts that simply use the Lucky CLI to notify the Lucky daemon of the hook's execution and of the environment variables that came with the hook execution. It is then the Lucky daemon's job to trigger the appropriate user scripts.

The Lucky Daemon and CLI

The Lucky daemon will be run by the charm install hook and will continue running for the whole duration that the charm is installed. The daemon will listen on a Unix socket for commands that will be sent to it by the Lucky CLI.

As noted above, the daemon will be notified for every Juju hook that is executed. It is the daemon's job to, in response to the hooks, trigger the charm developer's own scripts and to be those scripts' interface to both Docker and the Juju hook environment.

When a developer writes scripts for Lucky charms, instead of using the normal Juju commands such as set-status and get-relation provided by the Juju hook environment, the scripts will use the Lucky CLI. The reason for this is that scripts executed inside of the Docker container will not have access to the Juju hook environment. By mounting the Lucky daemon's socket and the Lucky CLI into the Docker container, we provide a way for scripts inside of the container to interact with the Juju hook environment. The Lucky daemon will also set helpful environment variables that can be used by the scripts, including the ones that exist in the Juju hook environment.

The CLI will also provide commands to configure how the container is run, such as adding environment variables, ports, starting/stopping the container, etc. The charm scripts will not themselves execute Docker commands directly, but will use the Lucky CLI instead.

Charm Scripts

The charm developer will write two kinds of scripts, host scripts and container scripts. Both kinds of are essentially similar to the normal Juju hooks and can be any executable format. All of a charms container scripts will be mounted into the Docker container and will execute inside of the container while all of the host scripts will be run on the host. The scripts will be executed by the Lucky daemon in response to the Juju hooks as outlined in a YAML file that could look something like this:

lucky.yml:

hooks:
  install:
    - host_script: do-some-host-stuff.sh
    - container_script: do-some-stuff-in-container.sh
  update-status:
    - host_script: health-check-service-from-host.sh
    - container_script: make-sure-process-in-container-is-running.py
  config-changed:
    - container_script: update-application-config.sh

The scripts will be executed by the Lucky daemon in the order that they are defined in the YAML.

Development

Building Lucky

Lucky is written in Rust and can be built with cargo by running

cargo build --release

The resulting binary named lucky will be in target/release.

Building Documentation

The documentation is built with mdbook. The documentation is located in the docs/book folder. To host the docs locally you can navigate to the docs/book dir and run mdbook serve.

CLI Documentation

In addition to the book content in docs/book, the CLI documentation is automatically built by Lucky and can be built by running the doc generator in the root of the project:

cargo run --features doc-gen docs/book/src/

Warning The SUMMARY.md file in docs/book/src will be automatically updated with the CLI documentation links when the doc generator is run. Do not put any links after the Lucky CLI link section or it will be overwritten.

Lucky - The Lucky charm framwork for Juju

Welcome to the Lucky CLI. Lucky is a framework for creating Juju charms. The Lucky framework is designed to make it easy and fun to write charms that are powered by Docker containers.

Usage

lucky [FLAGS] [SUBCOMMAND]

Subcommands

  • charm: Build and create Lucky charms
  • client: Communicate with Lucky and Juju in charm scripts

Development

Lucky is currently under active development and is in an alpha state. Features and documentation may be missing, but we at Katharos Technology are already producing charms with Lucky that are being used in the wild. Lucky is getting real testing and solving real problems.

If you have any thoughts or questions please don't hesitate to open a forum topic in the Lucky category on the Juju forum. You can also make feature requests or bug reports on our Taiga instance.

The Doc Pages and Help

Most of the commands in the Lucky CLI have an extra doc page, like this one, that can be accessed with the --doc or -H flag. These will usually have extra information and examples on how to use the command.

Another useful thing to know is that you will get different output by using the -h and --help flags. The -h flag will give you more compact help output while the --help flag will give you more details on the available options.

Getting Started

The first step to getting started with Lucky is to create your charm using the built-in charm template.

$ lucky charm create my-first-charm

You will be prompted for some basic fields that it will use to fill in the charm metadata. The doc page for lucky charm create has more information about what the different files in the charm are for.

After you have created your charm, you need to edit the lucky.yaml file to configure which scripts to run as a part of your charm. The charm template comes with some scripts and an example lucky.yaml file with comments that show the available options.

Once you have uncommented out the lines in the lucky.yaml you have to build the charm. Building the charm packages it so that it can be deployed to a Juju server or the charm store.

$ cd my-lucky-charm
$ lucky charm build

The build should complete almost immediately and you can then deploy the charm to a Juju controller:

$ juju deploy ./build/my-lucky-charm

Learning More

For a full tutorial you can read the Getting Started guide in the Lucky documentation.

Lucky Charm

The lucky charm command contains the tools you will need for charm development.

Usage

charm [FLAGS] [SUBCOMMAND]

Subcommands

  • build: Build a Lucky charm and make it ready for deployment
  • create: Create a new lucky charm

Getting Started

The lucky charm command contains tools for creating and building your charms. These are the minimal essential tools for Lucky charm developers. You can see the doc pages for the create and build subcommands to learn more.

Publishing Charms

In order to publish your charms to the charm store you will need the sepparate Juju charm tools. After you have the charm tools installed, you can push your charm to the charm store:

# Build the charm using Lucky
$ lucky charm build
# Login to the charm store
$ charm login
# Push the charm to the charm store
$ charm push ./build/my-charm my-charm

See the Juju documentation for more information about publishing charms.

Lucky Charm Build

Build a charm and make it ready for deployment.

Usage

build [FLAGS] [OPTIONS] [charm_dir]

Flags

ArgEnvironment VariableDescription
-l, --use-local-luckyBuild the charm bundled with the current version of Lucky. This is mostly useful during development. See "Building With Local Lucky" in the doc page.

Options

ArgEnvironment VariableDescription
-L, --log-level<log_level>The log level to build the charm with. Build with the log level set to "trace" to get more verbose logging from Lucky while the charm is running
default: debug
possible values: trace, debug, info, warn, error
-b, --build-dir<build_dir>The directory to put the built charm in. Defaults to the "build" directory in the charm dir. The built charm will be in "build_dir/charm_name".

Positionals

ArgEnvironment VariableDescription
<charm_dir>The path to the charm you want to build
default: .

Getting Started

The lucky charm build command will take a Lucky charm and package it so that it can be deployed to a Juju server or the charm store. By default, the charm will be built to the build/my-charm directory and can be deployed like so:

$ lucky charm build
$ juju deploy ./build/my-charm

Building With Local Lucky

When building with the --use-local-lucky or -l argument, Lucky will bundle the local version of Lucky that was used to build the charm into the built charm. This means that the charm will not attempt to download Lucky when it starts up and that the charm will only run on the same CPU architecture. This is mostly useful during development and only works on Linux builds made with the "daemon" feature.

If this is not specified, an automated build of Lucky for the architecture that the charm is deployed to will be automatically downloaded when the charm is installed.

Lucky Charm Create

Create a new Lucky charm from a template.

Usage

create [FLAGS] [OPTIONS] [target_dir]

Flags

ArgEnvironment VariableDescription
-D, --use-defaultsDo not prompt and use default values for unprovided fields

Options

ArgEnvironment VariableDescription
-n, --name<charm_name>The name of the charm. Defaults to the target_dir
-d, --display-name<display_name>The display name of the charm ( may contain spaces )
-s, --summary<charm_summary>Short description of the charm
-m, --maintainer<charm_maintainer>The charm maintainer

Positionals

ArgEnvironment VariableDescription
<target_dir>The directory to create the charm in

Getting Started

Running lucky charm create is the first step to getting started writing a Lucky charm. The command will prompt you for some basic information about your new charm and will then create all of the files necessary to get started.

Files

Here are the files you will need to modify to get started on your charm.

metadata.yaml

The metadata.yaml file has information about the charm such as name, description, and the kind of relations it supports. The Juju Documentation has more information on what can go in the metadata.yaml.

config.yaml

The config.yaml file outlines the different configuration options that users can provide to the charm. This might be used for things like admin passwords, web server ports, or other similar options. More information on charm config can be found in the Juju Config.

lucky.yaml

The lucky.yaml file is your "control panel" so to speak for what charm code gets executed when, in response to things like Juju hooks or cron jobs. Without telling Lucky to execute your charm scripts by adding entries to the lucky.yaml, your charm will not do anything.

The example lucky.yaml file that comes with the charm template has commented sections indicating all of the different kinds of entries you can add to the lucky.yaml.

host_scripts/ and container_scripts/

These directories contiain your charm scripts which represent the charm's logic. Each of the files in these dirs should be executable and are usually shell scripts starting with a proper "shabang": #!/bin/bash. The host_scripts dir contains scripts that are to be executed on the host system while container_scripts contains scripts that will be mounted into any containers that the charm runs.

These scripts will use the lucky client commands to interact with Juju, Lucky, and Docker. Se the lucky client doc page for more information.

Lucky Client

Interact with Juju, Lucky, and Docker in charm scripts.

Usage

client [FLAGS] <SUBCOMMAND>

Options

ArgEnvironment VariableDescription
-u, --unit-name<unit_name>JUJU_UNIT_NAMEThe name of the Juju unit that this charm is running for. This is not required if socket-path has been specified.
-s, --socket-path<socket_path>LUCKY_DAEMON_SOCKETThe path to the daemon socket. If this is left unspecified the socket path will be automatically determined from the unit name. For example, for a unit named mysql/2, the socket path will be /run/lucky_mysql_2.sock

Subcommands

  • set-status: Set the status of the current script
  • kv: Get and set values in the unit key-value store
  • container: Manipulate the charm's container(s)
  • public-address: Get the public address of the unit
  • private-address: Get the private IP address of the unit
  • get-config: Get the charm configuration
  • port: Open and close ports on the firewall
  • relation: Communicate over Juju relations
  • leader: Communicate as/with unit leader
  • random: Generate random numbers and strings
  • get-resource: Get the path to a Juju resource

Getting Started

The lucky client command contains every command that your charm scripts can use to interact with Juju, Lucky, and Docker.

It is important to realize that this command is only used in charm scipts and as help reference for the charm developer. Also, when using this command in charm scripts, you leave out the client portion of the command and just use lucky. For example, if you locally use lucky client set-status --help to find out what options the set-status command has, when you use it in your charm scripts, you just put lucky set-status, without the client.

Lucky Set-Status

Set the Juju status of the script.

Usage

set-status [FLAGS] --name <status_name> [ARGS]

Options

ArgEnvironment VariableDescription
-n, --name<status_name>LUCKY_SCRIPT_IDThe name of the status to set: defaults to the script's name. By default, setting the status will set the status only for the current script, leaving other statuses untouched. If you specify a name for the status other scripts can change that status by specifying the same name when calling set-status.

Positionals

ArgEnvironment VariableDescription
<state>The enumerated state of the service
possible values: active, waiting, maintenance, blocked
<message>An optional message to provide with the state

Usage

lucky set-status, by default, will set the status for the script and hook combination that it is run in. This means that running lucky set-status in one script, will not overwrite the satus set by another script. Also, if you set the status in a script that was triggered by one hook, it will not overwrite the status set by the same script in a different hook.

When multiple status are set at the same time, the Juju status will be set to a comma separated list of the statuses. This means that, when multiple scripts are running at the same time, you will not have to worry about their statuses getting overwritten by other scripts that are also trying to set the status.

It is typical in charm scripts do something like this:

# At the beginning of the script
lucky set-status maintenance "Doing something"

# Do stuff...

# At the end of the script
lucky set-status active

This will show that you are "Doing something" while the script is running, and then clear the status at the end of the script.

Setting the Status Name

When setting the status, you can specify the --name or -n flag to set a specific name for the status. While this name is not visible anywhere, it allows other scripts to set and override that specific status. This allows you to break out of the "each script sets it own status" design.

For example, if you set a status with the name global-status in an install.sh script, you can later change that status in another script by specifying its name.

Lucky KV

Get and set values in the unit-local key-value store.

Usage

kv [FLAGS] <SUBCOMMAND>

Subcommands

  • get: Get a value
  • set: Set key-value data

Usage

The lucky kv command allows you to interact with the unit's local key-value ( KV ) store. Because this KV store is local to the unit, setting a value in it will not have any effect on the KV store of any other unit in the app cluster. The KV store is a convenient way to maintain any kind of state that the charm might need to keep track of without having to read and write to files or relations.

They KV store will also persist across charm upgrades.

Examples

Set a value:

$ lucky kv set key=value

Set multiple values:

$ lucky kv set key1=value1 key2=value2 key3=value3

Get a value:

$ lucky kv get key1
value1

Get all values:

$ lucky kv get
key1=value1
key2=value2
key3=value3

Delete a value: Delete values by setting to nothing.

$ lucky kv set key3=

Lucky KV

Get values in the unit-local key-value store.

Usage

get [FLAGS] [key]

Positionals

ArgEnvironment VariableDescription
<key>The key to get from the store

Examples

Get a value:

$ lucky kv get key1
value1

Get all values:

$ lucky kv get
key1=value1
key2=value2
key3=value3

Lucky KV Set

Set values in the unit-local key-value store.

Usage

set [FLAGS] <data>...

Positionals

ArgEnvironment VariableDescription
<data>The data to set on the relation as key=value pairs separated by spaces

Examples

Set a value:

$ lucky kv set key=value

Set multiple values:

$ lucky kv set key1=value1 key2=value2 key3=value3

Set multiple values across multiple liines:

$ lucky kv set \
    key1=value1 \
    key2=value2 \
    key3=value3

Set values with spaces or newlines:

$ lucky kv set "key=value with spaces
and newlines"

Lucky Container

Control your Lucky containers.

Usage

container [FLAGS] <SUBCOMMAND>

Subcommands

  • image: Get and set the Docker image
  • apply-updates: Apply pending container configuration updates
  • env: Get and set container environment variables
  • set-entrypoint: Set the docker entrypoint
  • set-command: Set the docker command
  • volume: Configure container volumes
  • delete: Delete the docker container
  • port: Add and remove container port bindings
  • set-network: Set the docker network

Getting Started

The lucky container command provides an easy way to run and modify containers as a part of your charm. Most charms will only run one container, but you can run more if desired.

Lucky has the concept of a "default" container, which is the container that is assumed to be operated on by all of the lucky container subcommands. Other than the default contianer there can be any number of "named" containers. You can specify that a lucky container command should run on a specific named container with the --container or -c flags.

The first step to setting up a container is to set the container image:

$ lucky container image set nginx:latest
# Or if you wanted a named container
$ lucky container image set --container frontend nginx:latest

You must specify the image tag or digest when setting the image. Unlike the Docker CLI, Lucky will not assume you mean to deploy the latest tag if you do not specify a tag. If you fail to specify a tag you will get a Docker 404 error in the charm log and the charm script will error.

Once you have set the image for the container, you can use the other lucky container subcommands to set different attributes of the container. You can add environment variables, port bindings, and more.

# Add a port binding
$ lucky container port add 80:80
# Add an environment variable
$ lucky container env set PASSWORD=topsecret
# Add a volume
$ lucky volume add my-data:/data

Note: Not every attribute of containers can be set yet. If you have a need for a container feature that isn't there yet, it is very easy to add new ones, please create an issue and we will look into it.

How Containers are Run

It is important to understand that the changes to the container configuration made with the lucky container subcommands do not happen immediately. The changes are applied after the current charm script has exited. This allows the charm to make any desired changes to the config and to wait until it is done before making the updates to the container. Lucky is smart about when to apply the Docker updates: it will not do anything if the container configuration after running the script ends up the same as it was before running the script.

If you need to know that your container configuration changes have been applied before the script exits you can use the lucky container apply-updates command to force Lucky to apply the container config changes.

Re-deployment and Persistent Data

Whenever a container config update needs to be made, the existing container, if present, will be stopped and removed and a new container will be run with the desired configuration. This means any files changes made in the container will be lost if they are not persisted in a volume. See the volume subcommand for more information on volumes.

Container Removal

All running containers will be automatically stopped and removed by Lucky when the charm is removed. You can manually delete a container in your charm logic with lucky container delete.

Lucky Container Image

Create containers and set their image.

Usage

image [FLAGS] [SUBCOMMAND]

Subcommands

  • get: Get the container image
  • set: Set the container image

Usage

lucky container image allows you to set and get the Docker image for a container. The way you create new containers in Lucky is to set the container image with lucky container image set. After setting the image of the container, you can specify other settings such as environment variables and the container will be created when the script exits.

Note: The container tag or digest is required when setting the contianer image. Unlike Docker, Lucky will not assume that you mean to use the latest tag when you leave the tag unspecified.

Examples

Create a new Nginx container and bind 80 on the host:

$ lucky container image set nginx:latest
$ lucky container port add 80:80

get

Get the container image

Usage

get [FLAGS] [OPTIONS]

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

set

Set the container image

Usage

set [FLAGS] [OPTIONS] <image>

Flags

ArgEnvironment VariableDescription
--no-pullDon't attempt to pull image before running container

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<image>The container image

Lucky Container Apply-Updates

Apply changes to docker configuration in the middle of a charm script.

Usage

apply-updates [FLAGS]

Usage

The lucky container apply-updates command is used to apply any changes that have been made to the container configuration before the current script has exited. Normally Lucky will wait until your script has exited before it applies the container configuration, but this gives you a way to make sure that the updates have applied before executing further logic.

Examples

# Change an environment variable
lucky container env set PASSWORD=topsecret

# Apply the container configuration
lucky container apply-updates

# Continue doing stuff that depend on the container `PASSWORD` having been updated

Lucky Container Env

Get and set container environment variables.

Usage

env [FLAGS] [SUBCOMMAND]

Subcommands

  • get: Get an environment variable
  • set: Set environment variables

Examples

Set a var:

$ lucky container env set var=value

Set multiple vars:

$ lucky container env set var1=value1 var2=value2 var3=value3

Set vars with spaces or newlines in it:

$ lucky container env set "var1=value with space
and newline"

Get a var:

$ lucky container env get var1
value1

Get all vars:

$ lucky container env get
var1=value1
var2=value2
var3=value3

Delete a var: Delete values by setting to nothing.

$ lucky container env set var3=

get

Get an environment variable from the container. If you leave key unspecified, all environment variables will be printed out, one per line, in the format key=value.

Usage

get [FLAGS] [OPTIONS] [key]

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<key>The environment variable to get

set

Set environment variables

Usage

set [FLAGS] [OPTIONS] <vars>...

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<vars>The vars to set on the relation as key=value pairs separated by spaces. Setting values to nothing will remove the environment var.

Lucky Container Set-Entrypoint

Set the containers entrypoint.

Usage

set-entrypoint [FLAGS] [OPTIONS] [entrypoint]

Flags

ArgEnvironment VariableDescription
-u, --unsetUnset the entrypoint instead of setting it. The container will use the default entrypoint.

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<entrypoint>The entrypoint for the container

Example

Set the container entry point and command so that it will tail -f /dev/null:

$ lucky container set-entrypoint tail
# Because command arg starts with a `-` it must come after a lone `-` arg
$ lucky container set-command -- -f /dev/null

Lucky Container Set-command

Set the containers command.

Usage

set-command [FLAGS] [OPTIONS] [command]...

Flags

ArgEnvironment VariableDescription
-u, --unsetUnset the command instead of setting it. The container will use the default command.

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<command>The command for the container

Examples

The command args must come after a -- as shown in these examples:

Set command to echo hello:

$ lucky container set-command -- echo hello

Set command to example --arg:

$ lucky container set-command -- example --arg

Set the container entry point and command so that it will tail -f /dev/null:

$ lucky container set-entrypoint tail
$ lucky container set-command -- -f /dev/null

Lucky Container Volume

Add and remove volumes from the container.

Usage

volume [FLAGS] <SUBCOMMAND>

Subcommands

  • remove: Remove a volume from the container
  • add: Add a volume to the container
  • get: Get the list of volumes or the source for specific volume

Usage

The lucky container volume command can be used to add persistent volumes to your containers. The source for these volumes can be either an absolute path on the host or a named volume which will be automatically place in a dir in /var/lib/lucky/[unit_name]/volumes/[volume_name].

Warning: Lucky does not behave the same as Docker when mounting a new named volume to a non-empty directory in the container. If you mount a new named Lucky volume to a non-empty path in the container, the contents of that directory, in the container, will be masked by the empty volume that is being mounted to that location. This is contrary to Docker's behavior where a new named volume will inherit the initial contents of the target dir.

Examples

Mount /path/on/host to /data in the container:

$ lucky container volume add /path/on/host /data

Mount a volume named attachments to /var/lib/app/attachments in the container: The data will be persisted in a directory such as /var/lib/lucky/my_charm_3/volumes/attachments on the host.

$ lucky container volume add attachments /var/lib/app/attachments

Get the source path of a volume given the target path in the container:

$ lucky container volume get /data
/path/on/host

List all of the volumes:

$ lucky container volume get
/path/on/host:/data
attachments:/var/lib/app/attachments

Remove a volume, given the mountpoint in the container: This will not delete any data, it will just unmount it from the container.

$ lucky container volume remove /data

Remove a volume and its data: This will permanently delete any data in the volume.

$ lucky container volume remove --delete-data /var/lib/app/attachments

remove

Remove a volume from the container

Usage

remove [FLAGS] [OPTIONS] [container_path]

Flags

ArgEnvironment VariableDescription
-D, --delete-dataDelete the actual backing data for the volume

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<target>The absolute path in the container of the volume you want to remove.

add

Add a volume to the container.

NOTE: This command does not exhibit the same behaviour docker does when mounting an empty volume to an non-empty directory in the container. See the doc page for more info.

Usage

add [FLAGS] [OPTIONS] [ARGS]

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<source>The source for the volume: either a name for a named volume or an absolute path on the host
<target>The absolute path in the container to mount source to

get

If target is provided and the volume exists, the source of the volume will be printed. If target is not provided, a list of volumes in the format source:target will be printed.

Usage

get [FLAGS] [OPTIONS] [container_path]

Options

ArgEnvironment VariableDescription
-c, --container<container>The name of the container to delete the environment variable for

Positionals

ArgEnvironment VariableDescription
<target>The absolute path, in the container, to the volume you want to get

Lucky Container Delete

Delete a container.

Usage

delete [FLAGS] [OPTIONS]

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Usage

lucky container delete will remove a container. Like all lucky container commands, you can specify which container to delete with the --container flag. Also like the other container commands, the container will be deleted after the current script exits unless you force the update with lucky container apply-updates.

Examples

# Delete the default container
lucky container delete

Lucky Container Port

Add, remove, and list container port bindings.

Usage

port [FLAGS] [SUBCOMMAND]

Subcommands

  • add: Add a port binding
  • remove: Remove a port binding
  • list: Get a list of the container's port bindings

Usage

lucky container port allows you to control which ports in the container get bound to which ports on the host.

It is important to understand that adding port bindings with lucky container port add will append the port binding to any existing port bindings. If you want to make sure that the contianer only has the bindings that you specify at a particular moment in time you must first run lucky container port remove --all.

Examples

Bind port 80 on the host to 80 in the container:

$ lucky container port add 80:80

Bind port 8080 on the host to 80 in the container:

$ lucky container port add 8080:80

Remove the port binding of 80 on the host to 80 in the contianer:

$ lucky container port remove 

Get a list of the port bindings for the container:

$ lucky container port list
80:80
443:443

Make sure the only port bindings on the container are 80:80 and 443:443 :

$ lucky container port remove --all
$ lucky container port add 80:80
$ lucky container port add 443:443

add

Add a port binding

Usage

add [FLAGS] [OPTIONS] [port_binding]

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<port_binding>The port binding to add in the format: host_port:container_port/proto. the /proto suffix is optional and defaults to /tcp.

remove

Remove a port binding

Usage

remove [FLAGS] [OPTIONS] [port_binding]

Flags

ArgEnvironment VariableDescription
-A, --allRemove all port bindings from the container

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<port_binding>The port binding to remove in the format: host_port:container_port/proto. the /proto suffix is optional and defaults to /tcp.

list

Get a list of the container's port bindings

Usage

list [FLAGS] [OPTIONS]

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Lucky Container Set-Network

Set the docker network to connect the container to.

Usage

set-network [FLAGS] [OPTIONS] [network]

Flags

ArgEnvironment VariableDescription
-u, --unsetUnset the network instead of setting it. The container will use the default bridge network.

Options

ArgEnvironment VariableDescription
-c, --container<name>The name of the container to update. If not specified the default container will be used

Positionals

ArgEnvironment VariableDescription
<network>The network for the container to use

Usage

The lucky container set-network command is primarily intended to allow you to run the container in host networking mode:

$ lucky container set-network host

This means that you do not need to bind any ports for the container and that any apps running inside the container will run, from a network perspective, just as they would if they were run on the host.

Lucky Public-Address

Get the unit's public address.

Usage

public-address [FLAGS]

Usage

lucky public-addresss will return the public address of the current unit. The server should be accessible from the internet by the public address. Before users can connect to your app from the internet, though, you will need to open ports using lucky port open and the user of the charm will need to expose the app with juju expose my-app.

Lucky Private-Address

Get the unit's private address.

Usage

private-address [FLAGS]

Usage

lucky private-addresss will return the private address of the current unit. Juju will make sure that the servers in a model will be able to communicate with each-other over their private addresses.

Lucky Get-Config

Get values from the charm config.

Usage

get-config [FLAGS] [key]

Positionals

ArgEnvironment VariableDescription
<key>The config key to get. If not specified all config values will be returned, one per line, in the format key=value.

Usage

lucky get-config is used to get values from the charm configuration. The charm configuration available is defined the the charm's config.yaml file. This config allows users to provide input to how the should function.

See the Juju documentation for more information on the config.yaml file.

Examples

Get the my-app-version config:

$ lucky get-config my-app-version
1.33.4

This assumes that there is a my-app-version config in the config.yaml file, which could look something like this:

config.yaml:

options:
  my-app-version:
    type: string
    default: 1.33.0
    description: The version of My App to install

Lucky Port

Open and close ports on the host firewall.

Usage

port [FLAGS] <SUBCOMMAND>

Subcommands

  • open: Open a port on the firewall
  • close: Close a port on the firewall
  • get-opened: Get the ports opened by this charm

Usage

The lucky port command allows you to open, close, and get-opened ports on the host firewall.

Note: Juju will not actually expose the opened firewall ports unless the user deploying the charm exposes it using juju expose my-app. In this way, apps are not accidentally exposed to the internet without the user meaning to.

Examples

Make sure your configured port is the only port opened:

# Make sure any previously opened ports are closed
$ lucky port close --all
# Open the new ports
$ lucky port open $(lucky get-config listen-port)

open

Open a port on the firewall. A port range may be specified with an optional protocol as well ( TCP is default ). Example values: 8000-9000/udp, 9000.

Usage

open [FLAGS] <port>

Positionals

ArgEnvironment VariableDescription
<port>The port to open

close

Close a port on the firewall. A port range may be specified with an optional protocol as well ( TCP is default ). Example values: 8000-9000/udp, 9000.

Usage

close [FLAGS] [port]

Flags

ArgEnvironment VariableDescription
-A, --allRemove all port rules

Positionals

ArgEnvironment VariableDescription
<port>The port to close

get-opened

Get the ports opened by this charm. This will return a line for each port or port range that has been opened in the format: port-or-range/protocol. For exmaple:

8000/tcp 10000-11000/udp

Usage

get-opened [FLAGS]

Lucky Relation

Communicate over Juju relations.

Usage

relation [FLAGS] <SUBCOMMAND>

Subcommands

  • get: Get data from a relation
  • set: Set local values on a relation
  • list-units: List the units collected to a relation
  • list-ids: List relation ids connected to this unit

Understanding Relations

The lucky relation command has all of the tools that you need to interact with Juju's concept of relations. Relations are a somwhat complicated concept to grasp but they are probably the most important concept in Juju and they are what powers Juju's ability to deploy apps in a conceptually simple manner.

Juju relations allow the units on each side of a relation to set values on their side of the relation that can be read by the unit on the other side. A unit can set and get values on its own side of the relation, but it can only get values from the other side of the relation.

While we hope to provide more documentation on using relations later, currently the best reference is the Juju documentation. The lucky relation commands serve the same functions as the built-in Juju hook tools. The mappings are as follows:

Lucky CommandJuju Command
lucky relation setrelation-set
lucky relation getrelation-get
lucky relation list-idsrelation-ids
lucky relation list-unitsrelation-list

Examples

Iterating over charm relations:

This will work inside or outside of a relation-* hook.

servers=""
# For each related application in the `http` relation
for relation_id in $(lucky relation list-ids --relation-name http); do
    
    # For every unit of that application
    for related_unit in $(lucky relation list-units -r $relation_id); do
        
        # Get the unit's hostname
        addr=$(lucky relation get -r $relation_id -u $related_unit hostname)
        
        # Get the unit's port
        port=$(lucky relation get -r $relation_id -u $related_unit port)
        
        # Add it to the server list
        servers="$servers $addr:$port"

        # You can also set values on your side of the relation
        lucky relation set -r $relation_id -u $related_unit key=value
    done

    # You can also get the values that you have set on this relation by specifying
    # your unit name ( from the $JUJU_UNIT_NAME env var ) as the -u argument
    lucky relation get -r $relation_id -u $JUJU_UNIT_NAME hostname
done

Get the hostname and port of a related unit in a http-relation-changed hook:

In this example, because we are in a http-relation-changed hook, we don't have to specify the relation id or the related unit. Those are set in environment variables by Juju and the lucky relation get command will automatically default to getting from the relation and unit that triggered the relation-changed hook.

hostname=$(lucky relation get hostname)
port=$(lucky relation get port)

You can also set data on relations in hooks in the same way.

lucky relation set user=my-username

get

Get data from a relation

Usage

get [FLAGS] [OPTIONS] [key]

Flags

ArgEnvironment VariableDescription
-A, --appGet application relation data instead of unit relation data

Options

ArgEnvironment VariableDescription
-r, --relation-id<relation_id>The relation id to get the data from
-u, --remote-unit<remote_unit_name>The name of the remote unit to get data from

Positionals

ArgEnvironment VariableDescription
<key>Optional key to get from the data

set

Set local values on a relation

Usage

set [FLAGS] [OPTIONS] <data>...

Flags

ArgEnvironment VariableDescription
-A, --appSet application relation data instead of unit relation data. the unit must be the leader unit to set application relation data

Options

ArgEnvironment VariableDescription
-r, --relation-id<relation_id>The relation id to get the data from

Positionals

ArgEnvironment VariableDescription
<data>The data to set on the relation as key=value pairs separated by spaces

list-units

List the units collected to a relation

Usage

list-units [FLAGS] [OPTIONS]

Options

ArgEnvironment VariableDescription
-r, --relation-id<relation_id>The relation id to list the connected units for

list-ids

List relation ids connected to this unit

Usage

list-ids [FLAGS] [OPTIONS]

Options

ArgEnvironment VariableDescription
-n, --relation-name<relation_name>The specific name of the relation ( from the metadata.yaml )

Lucky Leader

Interact with the Juju leader system.

Usage

leader [FLAGS] <SUBCOMMAND>

Subcommands

  • get: Get data from the leader unit
  • set: Set values as the leader charm
  • is-leader: Get whether or not this unit is the leader unit

Usage

The lucky leader command provides a way to interract with Juju's leader system. You can get, set, and check whether or not the current user is-leader.

The leader unit, elected by Juju, is allowed to set key-value pair using lucky leader set and all of the units in the app are able to read key-value pairs from the leader by using lucky leader get. See the Juju documentation for more information on how to use leadership in Juju.

Examples

Set a random password if the unit is the leader:

if [ "$(lucky leader is-leader)" = "true" ]; then
    lucky leader set password=$(lucky random)
fi

Get the password from the leader unit:

$ lucky leader get password

Set multiple leader values:

$ lucky leader set \
    user=username \
    password=topsecret

get

Get data from the leader unit

Usage

get [FLAGS] [key]

Positionals

ArgEnvironment VariableDescription
<key>Optional key to get from the data

set

Set values as the leader charm

Usage

set [FLAGS] <data>...

Positionals

ArgEnvironment VariableDescription
<data>The data to set as the leader as key=value pairs separated by spaces

is-leader

Get whether or not this unit is the leader unit. Returns "true" if unit is leader and "false" if it is not.

Usage

is-leader [FLAGS]

Lucky Random

Generate random passwords, numbers, and available ports.

Usage

random [FLAGS] [OPTIONS]

Flags

ArgEnvironment VariableDescription
-p, --available-portGet a random available port. This may be combined with --range to get a random available port in the given range. The default port range is 1024-65535. If an available port cannot be found in the given range the nextavailable port starting at 1024 will be selected
-f, --floatGenerate a floating point range instead of an integer range

Options

ArgEnvironment VariableDescription
-r, --range<lowest_number> <highest_number>Generate a random integer in a range
-l, --length<length>Set the length of the generated string

Usage

lucky random is a utility for generating passwords, number ranges, and for getting random available ports.

Examples

Generate a random password with a specified length:

$ lucky random --length 32

Generate a random integer between 0 and 10 inclusive:

$ lucky random --range 0 10

Generate a random float between 0 and 1:

$ lucky random --float --range 0 1

Find a random available port in between 1024 and 65535:

$ lucky random --available-port

Lucky Get-Resource

Get the path to a Juju resource.

Usage

get-resource [FLAGS] [resource_name]

Positionals

ArgEnvironment VariableDescription
<resource_name>The name of the resource to get as defined in the metadata.yaml file

Usage

This command is the direct equivalent to Juju's resource-get command. Refer to that documentation for more information on how Juju resources work.

This command will exit non-zero if the resource has not been uploaded yet.

Example

Make sure a resource exists before getting the path to it:

if ! lucky get-resource resource-name; then
    lucky set-status blocked "Need resource-name to be uploaded"
else
    path_to_resource=$(lucky get-resource resource-name)
fi