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 weprovide
and the way we interact with that relation conforms to thehttp
interface. - we have a
database
relation that werequire
and the way we interact with that relation conforms to thepgsql
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 runlucky 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
orlucky 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 thehttp
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.
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 indocs/book/src
will be automatically updated with the CLI documentation links when the doc generator is run. Do not put any links after theLucky 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
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
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
Arg | Environment Variable | Description |
---|---|---|
-l , --use-local-lucky | Build 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
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-D , --use-defaults | Do not prompt and use default values for unprovided fields |
Options
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-u , --unit-name <unit_name> | JUJU_UNIT_NAME | The 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_SOCKET | The 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
Arg | Environment Variable | Description |
---|---|---|
-n , --name <status_name> | LUCKY_SCRIPT_ID | The 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
Arg | Environment Variable | Description |
---|---|---|
<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
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
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
<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
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
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
--no-pull | Don't attempt to pull image before running container |
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
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
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<key> | The environment variable to get |
set
Set environment variables
Usage
set [FLAGS] [OPTIONS] <vars>...
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-u , --unset | Unset the entrypoint instead of setting it. The container will use the default entrypoint. |
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-u , --unset | Unset the command instead of setting it. The container will use the default command. |
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-D , --delete-data | Delete the actual backing data for the volume |
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-c , --container <container> | The name of the container to delete the environment variable for |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-A , --all | Remove all port bindings from the container |
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
-u , --unset | Unset the network instead of setting it. The container will use the default bridge network. |
Options
Arg | Environment Variable | Description |
---|---|---|
-c , --container <name> | The name of the container to update. If not specified the default container will be used |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-A , --all | Remove all port rules |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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 Command | Juju Command |
---|---|
lucky relation set | relation-set |
lucky relation get | relation-get |
lucky relation list-ids | relation-ids |
lucky relation list-units | relation-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
Arg | Environment Variable | Description |
---|---|---|
-A , --app | Get application relation data instead of unit relation data |
Options
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
<key> | Optional key to get from the data |
set
Set local values on a relation
Usage
set [FLAGS] [OPTIONS] <data>...
Flags
Arg | Environment Variable | Description |
---|---|---|
-A , --app | Set application relation data instead of unit relation data. the unit must be the leader unit to set application relation data |
Options
Arg | Environment Variable | Description |
---|---|---|
-r , --relation-id <relation_id> | The relation id to get the data from |
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
<key> | Optional key to get from the data |
set
Set values as the leader charm
Usage
set [FLAGS] <data>...
Positionals
Arg | Environment Variable | Description |
---|---|---|
<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
Arg | Environment Variable | Description |
---|---|---|
-p , --available-port | Get 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 , --float | Generate a floating point range instead of an integer range |
Options
Arg | Environment Variable | Description |
---|---|---|
-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
Arg | Environment Variable | Description |
---|---|---|
<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