Get started with the Juju SDK - Kubernetes

Note: This doc has been superseded by From zero to hero: Write your first Kubernetes charm . Please see that doc instead.


This tutorial will introduce you to writing a Kubernetes charm with the Juju SDK.

Prerequisites:

  • A working station, e.g., a laptop.
  • Familiarity with OOP in Python.

Contents:

Create the test environment

The Juju SDK is currently supported only on Linux. However, you can also use it on macOS or Windows by quickly creating an Ubuntu virtual machine with Multipass. This is also a good option for people who would like an isolated test environment on their existing Linux system.

First, install Multipass: Linux | macOS | Windows.

Then, open a terminal and use Multipass to launch an Ubuntu virtual machine and open a shell in it, as shown below. We’ve called ours tutorial-vm .

# Launch a VM  "tutorial-vm" with 8 GB RAM, 2 CPU cores, and 20 GB disk:
$ multipass launch -n tutorial-vm -m 8g -c 2 -d 20G
Launched: tutorial-vm

# Open a shell inside the VM:
$ multipass shell tutorial-vm
ubuntu@tutorial-vm:~$

Congratulations, your Ubuntu virtual machine is ready! Any command you type after the last terminal prompt will be inside of this virtual machine. Let’s give it a try by running the commands below. If they work, they will not just confirm that your virtual machine is working—they will also configure it so it will work properly in the rest of this tutorial.

# Install 'tree' and 'unzip':
ubuntu@tutorial-vm:~$ sudo apt update && sudo apt install tree unzip 

If for whatever reason you need to interrupt this tutorial, we recommend running multipass stop tutorial-vm to stop the instance and then multipass start tutorial-vm / multipass shell tutorial-vm to start / start and open a shell inside the instance, when you want to resume.

Install the Charmcraft CLI

We’ve got our Ubuntu test environment. We are now ready to install the Juju SDK CLI tool, charmcraft:

# Configure LXD:
$ lxd init --auto
# Install Charmcraft:
$ sudo snap install charmcraft --classic

Done!

Set up your charm project

We’ve got charmcraft. Time to set up our charm project!

First, create a directory named after your intended charm. We will call ours hello-world.

$ mkdir hello-world

Second, navigate inside that directory and run charmcraft init. As the output indicates, this has created all the directories and files that you will need to write your charm.

$ cd hello-world
$ charmcraft init
Charm operator package file and directory tree initialized.                                                                                                                                                                                  
TODO:                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
      README.md: Describe your charm in a few paragraphs of Markdown                                                                                                                                                                        
      README.md: Provide high-level usage, such as required config or relations                                                                                                                                                              
      README.md: Provide any relations which are provided or required by your charm                                                                                                                                                          
      README.md: Include a link to the default image your charm uses                                                                                                                                                                        
   actions.yaml: change this example to suit your needs.                                                                                                                                                                                    
    config.yaml: change this example to suit your needs.                                                                                                                                                                                    
  metadata.yaml: fill out a display name for the Charmcraft store                                                                                                                                                                            
  metadata.yaml: fill out the charm's description                                                                                                                                                                                            
  metadata.yaml: fill out the charm's summary                                                                                                                                                                                                
  metadata.yaml: replace with containers for your workload (delete for non-k8s)                                                                                                                                                              
  metadata.yaml: each container defined above must specify an oci-image resource                                                                                                                                                            
   src/charm.py: change this example to suit your needs.                                                                                                                                                                                    
   src/charm.py: change this example to suit your needs.                                                                                                                                                                                    
   src/charm.py: change this example to suit your needs.       

You can also verify this by inspecting your charm directory using tree, as shown below:

$ tree
.
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── actions.yaml
├── charmcraft.yaml
├── config.yaml
├── metadata.yaml
├── requirements-dev.txt
├── requirements.txt
├── run_tests
├── src
│   └── charm.py
└── tests
    ├── __init__.py
    └── test_charm.py

2 directories, 13 files

Congratulations, you are ready to start writing your charm!

Write your charm

In the previous section you used Charmcraft to initialise your charm project. This created a number of files. To build a charm, you will need to start editing some of those files, most crucially src/charm.py, the metadata.yaml, and requirements.txt.

Edit the src/charm.py file

The first file you’ll want to edit is the src/charm.py file, which is the place where you put the code for your charm.

To begin with, open src/charm.py in edit mode.

$ edit src/charm.py

Then, delete all the contents of the file.

The file is pre-populated with scaffolding for creating a Kubernetes charm. However, for educational purposes, it is easiest to start from scratch.

Finally, start writing your own Python code, as follows:

First, add the code below to import the logging module from Python’s stdlib library.

#!/usr/bin/env python3

import logging

Then, also add the code below to import the CharmBase class and, respectively, the main function from the charm and, respectively, the main module of the ops framework.

ops is a part of the Juju SDK. It was installed on your system when you installed charmcraft. See Ops and Ops API reference.

from ops.charm import CharmBase
from ops.main import main

Now, use the logging module to create a logger.

logger = logging.getLogger(__name__)

Also, start creating your own charm class. This will be a straightforward Python class which inherits from CharmBase and which we pass to the framework’s main.

class MyCharm(CharmBase):

In the scope of this class, inside the constructor, register a handler self.on_install for the self.on.install event:

    def __init__(self, *args):
        super().__init__(*args)
        self.framework.observe(self.on.install, self.on_install)

Also in the scope of this class, define the on_install event handler. To keep things simple, just make it log a simple greeting message.

    def on_install(self, event):
        logger.info("Congratulations, the charm was properly installed!")

Finally, outside of the scope of the class, register the main function, to call your charm.

if __name__ == "__main__":
    main(MyCharm)
Expand to see all the code above at once
#!/usr/bin/env python3

import logging

from ops.charm import CharmBase
from ops.main import main

logger = logging.getLogger(__name__)


class MyCharm(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.framework.observe(self.on.install, self.on_install)

    def on_install(self, event):
        logger.info("Congratulations, the charm was properly installed!")


if __name__ == "__main__":
    main(MyCharm)

Congratulations, you have a minimal functioning version of your src/charm.py file!

Edit the metadata.yaml file

The second file you should edit is the metadata.yaml file. This file is where you put information that will later help the Juju Operator Lifecycle Manager (OLM) to correctly manage your charm.

As before, let’s delete all the pre-existing contents of this file, so you can see the process from scratch.

Now, add the following:

name: hello-world
summary: My first operator from the tutorial
description: |
  A very simple charm to demonstrate Charmcraft and the
  Python operator framework Ops.
assumes:
  - k8s-api

The most important piece here is the assumes keyword, specifying the k8s-api. This ensures that our charm will be able to run on Kubernetes.

Edit the requirements.txt file

Any Python dependencies are declared in the requirements.txt file. In our example, we only need the Operator Framework (ops). This dependency is automatically added when you initialize your charm project with charmcraft init:

$ cat requirements.txt 
ops >= 1.4.0

You can add other Pypi dependencies to that file. charmcraft will keep them up to date as part of the build process.

Pack your charm

Your operator is pure code. To share it with someone else, it helps to have a package for it, which we call a charm. A charm of an operator is like a deb of a binary: a package that can be shared, published and retrieved.

Let’s pack our charm:

$ charmcraft pack
Packing the charm                                                                                                                                                                                                                            
Created 'hello-world_ubuntu-20.04-amd64.charm'.                                                                                                                                                                                              
Charms packed:                                                                                                                                                                                                                              
    hello-world_ubuntu-20.04-amd64.charm    

As the output indicates, the result is a file called hello-world_ubuntu-20.04-amd64.charm.

This name might vary slightly, depending on your architecture. E.g., for an arm processor, you will see arm64 rather than amd64. In the commands below make sure to enter the correct charm name.

Note in particular the .charm extension. This is really just a zip file of the code and all its dependencies, as well as the metadata. charmcraft pack just fetches the dependencies, compiles any modules, makes sure you have all the right pieces of metadata, and zips it up for easy distribution.

Take a look inside. You’ll recognise your files, and then a virtual environemnt with the Python dependencies clearly mapped out:

Expand to see the list of files
# Unzip your charm:
$ unzip -l hello-world_ubuntu-20.04-amd64.charm
Archive:  hello-world_ubuntu-20.04-amd64.charm
  Length      Date    Time    Name
---------  ---------- -----   ----
    11358  2022-07-12 15:41   LICENSE
      422  2022-07-12 15:41   config.yaml
      534  2022-07-12 15:41   README.md
      250  2022-07-12 15:51   manifest.yaml
      475  2022-07-12 15:41   actions.yaml
      102  2022-07-12 15:48   dispatch
      188  2022-07-12 15:44   metadata.yaml
      102  2022-07-12 15:48   hooks/start
      102  2022-07-12 15:48   hooks/install
      102  2022-07-12 15:48   hooks/upgrade-charm
      126  2022-07-12 15:48   venv/easy_install.py
      125  2022-07-12 15:48   venv/pip-20.0.2.dist-info/entry_points.txt
      110  2022-07-12 15:48   venv/pip-20.0.2.dist-info/WHEEL
     3352  2022-07-12 15:48   venv/pip-20.0.2.dist-info/METADATA
     1090  2022-07-12 15:48   venv/pip-20.0.2.dist-info/LICENSE.txt
    18528  2022-07-12 15:48   venv/pip-20.0.2.dist-info/RECORD
        4  2022-07-12 15:48   venv/pip-20.0.2.dist-info/top_level.txt
        4  2022-07-12 15:48   venv/pip-20.0.2.dist-info/INSTALLER
     3150  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/entry_points.txt
    21518  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/AUTHORS.txt
      110  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/WHEEL
     3523  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/METADATA
     1090  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/LICENSE.txt
    11941  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/RECORD
       38  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/top_level.txt
        1  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/zip-safe
        4  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/INSTALLER
      239  2022-07-12 15:48   venv/setuptools-44.0.0.dist-info/dependency_links.txt
     4883  2022-07-12 15:50   venv/yaml/composer.py
    51279  2022-07-12 15:50   venv/yaml/scanner.py
    14190  2022-07-12 15:50   venv/yaml/representer.py
     9004  2022-07-12 15:50   venv/yaml/resolver.py
     2533  2022-07-12 15:50   venv/yaml/error.py
    12309  2022-07-12 15:50   venv/yaml/__init__.py
     6794  2022-07-12 15:50   venv/yaml/reader.py
     2061  2022-07-12 15:50   venv/yaml/loader.py
     1440  2022-07-12 15:50   venv/yaml/nodes.py
     2573  2022-07-12 15:50   venv/yaml/tokens.py
     3851  2022-07-12 15:50   venv/yaml/cyaml.py
    28639  2022-07-12 15:50   venv/yaml/constructor.py
    25495  2022-07-12 15:50   venv/yaml/parser.py
     2837  2022-07-12 15:50   venv/yaml/dumper.py
     4165  2022-07-12 15:50   venv/yaml/serializer.py
     2445  2022-07-12 15:50   venv/yaml/events.py
    43006  2022-07-12 15:50   venv/yaml/emitter.py
    25359  2022-07-12 15:50   venv/yaml/__pycache__/emitter.cpython-38.pyc
     2306  2022-07-12 15:50   venv/yaml/__pycache__/error.cpython-38.pyc
     3417  2022-07-12 15:50   venv/yaml/__pycache__/cyaml.cpython-38.pyc
     4941  2022-07-12 15:50   venv/yaml/__pycache__/tokens.cpython-38.pyc
    11119  2022-07-12 15:50   venv/yaml/__pycache__/__init__.cpython-38.pyc
     1731  2022-07-12 15:50   venv/yaml/__pycache__/nodes.cpython-38.pyc
    11930  2022-07-12 15:50   venv/yaml/__pycache__/parser.cpython-38.pyc
     4543  2022-07-12 15:50   venv/yaml/__pycache__/reader.cpython-38.pyc
    10075  2022-07-12 15:50   venv/yaml/__pycache__/representer.cpython-38.pyc
    25277  2022-07-12 15:50   venv/yaml/__pycache__/scanner.cpython-38.pyc
     3980  2022-07-12 15:50   venv/yaml/__pycache__/events.cpython-38.pyc
     3326  2022-07-12 15:50   venv/yaml/__pycache__/serializer.cpython-38.pyc
     1829  2022-07-12 15:50   venv/yaml/__pycache__/dumper.cpython-38.pyc
    20828  2022-07-12 15:50   venv/yaml/__pycache__/constructor.cpython-38.pyc
     5512  2022-07-12 15:50   venv/yaml/__pycache__/resolver.cpython-38.pyc
     2170  2022-07-12 15:50   venv/yaml/__pycache__/loader.cpython-38.pyc
     3569  2022-07-12 15:50   venv/yaml/__pycache__/composer.cpython-38.pyc
      455  2022-07-12 15:48   venv/pip/__init__.py
      632  2022-07-12 15:48   venv/pip/__main__.py
     4975  2022-07-12 15:48   venv/pip/_vendor/__init__.py
     3037  2022-07-12 15:48   venv/pip/_vendor/__pycache__/__init__.cpython-38.pyc
    11605  2022-07-12 15:48   venv/pip/_internal/cache.py
    16277  2022-07-12 15:48   venv/pip/_internal/legacy_resolve.py
     5490  2022-07-12 15:48   venv/pip/_internal/pep425tags.py
      437  2022-07-12 15:48   venv/pip/_internal/main.py
      517  2022-07-12 15:48   venv/pip/_internal/__init__.py
     8009  2022-07-12 15:48   venv/pip/_internal/self_outdated_check.py
     6734  2022-07-12 15:48   venv/pip/_internal/locations.py
    10247  2022-07-12 15:48   venv/pip/_internal/exceptions.py
    14222  2022-07-12 15:48   venv/pip/_internal/configuration.py
     9441  2022-07-12 15:48   venv/pip/_internal/wheel_builder.py
     7532  2022-07-12 15:48   venv/pip/_internal/build_env.py
     7400  2022-07-12 15:48   venv/pip/_internal/pyproject.py
      760  2022-07-12 15:48   venv/pip/_internal/distributions/installed.py
      959  2022-07-12 15:48   venv/pip/_internal/distributions/__init__.py
     4086  2022-07-12 15:48   venv/pip/_internal/distributions/sdist.py
     1294  2022-07-12 15:48   venv/pip/_internal/distributions/wheel.py
     1425  2022-07-12 15:48   venv/pip/_internal/distributions/base.py
     1208  2022-07-12 15:48   venv/pip/_internal/distributions/__pycache__/installed.cpython-38.pyc
     1928  2022-07-12 15:48   venv/pip/_internal/distributions/__pycache__/base.cpython-38.pyc
      812  2022-07-12 15:48   venv/pip/_internal/distributions/__pycache__/__init__.cpython-38.pyc
     1560  2022-07-12 15:48   venv/pip/_internal/distributions/__pycache__/wheel.cpython-38.pyc
     3471  2022-07-12 15:48   venv/pip/_internal/distributions/__pycache__/sdist.cpython-38.pyc
    28114  2022-07-12 15:48   venv/pip/_internal/cli/cmdoptions.py
     2610  2022-07-12 15:48   venv/pip/_internal/cli/main.py
     2819  2022-07-12 15:48   venv/pip/_internal/cli/main_parser.py
      132  2022-07-12 15:48   venv/pip/_internal/cli/__init__.py
     7948  2022-07-12 15:48   venv/pip/_internal/cli/base_command.py
      156  2022-07-12 15:48   venv/pip/_internal/cli/status_codes.py
    12463  2022-07-12 15:48   venv/pip/_internal/cli/req_command.py
     6547  2022-07-12 15:48   venv/pip/_internal/cli/autocompletion.py
     9487  2022-07-12 15:48   venv/pip/_internal/cli/parser.py
      975  2022-07-12 15:48   venv/pip/_internal/cli/command_context.py
     8289  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/req_command.cpython-38.pyc
     1406  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/main.cpython-38.pyc
      365  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/status_codes.cpython-38.pyc
      236  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/__init__.cpython-38.pyc
     8977  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/parser.cpython-38.pyc
     1311  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/command_context.cpython-38.pyc
     2159  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/main_parser.cpython-38.pyc
     4953  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/autocompletion.cpython-38.pyc
     5847  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/base_command.cpython-38.pyc
    20328  2022-07-12 15:48   venv/pip/_internal/cli/__pycache__/cmdoptions.cpython-38.pyc
      617  2022-07-12 15:48   venv/pip/_internal/vcs/__init__.py
     5110  2022-07-12 15:48   venv/pip/_internal/vcs/mercurial.py
    12292  2022-07-12 15:48   venv/pip/_internal/vcs/subversion.py
    14352  2022-07-12 15:48   venv/pip/_internal/vcs/git.py
     3957  2022-07-12 15:48   venv/pip/_internal/vcs/bazaar.py
    22600  2022-07-12 15:48   venv/pip/_internal/vcs/versioncontrol.py
     3747  2022-07-12 15:48   venv/pip/_internal/vcs/__pycache__/bazaar.cpython-38.pyc
     8487  2022-07-12 15:48   venv/pip/_internal/vcs/__pycache__/subversion.cpython-38.pyc
      448  2022-07-12 15:48   venv/pip/_internal/vcs/__pycache__/__init__.cpython-38.pyc
     9559  2022-07-12 15:48   venv/pip/_internal/vcs/__pycache__/git.cpython-38.pyc
     4888  2022-07-12 15:48   venv/pip/_internal/vcs/__pycache__/mercurial.cpython-38.pyc
    19216  2022-07-12 15:48   venv/pip/_internal/vcs/__pycache__/versioncontrol.cpython-38.pyc
    17892  2022-07-12 15:48   venv/pip/_internal/index/collector.py
       30  2022-07-12 15:48   venv/pip/_internal/index/__init__.py
    37542  2022-07-12 15:48   venv/pip/_internal/index/package_finder.py
    14156  2022-07-12 15:48   venv/pip/_internal/index/__pycache__/collector.cpython-38.pyc
      190  2022-07-12 15:48   venv/pip/_internal/index/__pycache__/__init__.cpython-38.pyc
    25749  2022-07-12 15:48   venv/pip/_internal/index/__pycache__/package_finder.cpython-38.pyc
    30442  2022-07-12 15:48   venv/pip/_internal/req/req_install.py
    18485  2022-07-12 15:48   venv/pip/_internal/req/req_file.py
    14388  2022-07-12 15:48   venv/pip/_internal/req/constructors.py
    23609  2022-07-12 15:48   venv/pip/_internal/req/req_uninstall.py
     2671  2022-07-12 15:48   venv/pip/_internal/req/__init__.py
     8066  2022-07-12 15:48   venv/pip/_internal/req/req_set.py
     4723  2022-07-12 15:48   venv/pip/_internal/req/req_tracker.py
    10363  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/constructors.cpython-38.pyc
     4041  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/req_tracker.cpython-38.pyc
    21338  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/req_install.cpython-38.pyc
     2194  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/__init__.cpython-38.pyc
    12702  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/req_file.cpython-38.pyc
    17427  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/req_uninstall.cpython-38.pyc
     6021  2022-07-12 15:48   venv/pip/_internal/req/__pycache__/req_set.cpython-38.pyc
     3799  2022-07-12 15:48   venv/pip/_internal/models/target_python.py
     6827  2022-07-12 15:48   venv/pip/_internal/models/link.py
       63  2022-07-12 15:48   venv/pip/_internal/models/__init__.py
     1060  2022-07-12 15:48   venv/pip/_internal/models/index.py
      679  2022-07-12 15:48   venv/pip/_internal/models/scheme.py
     2766  2022-07-12 15:48   venv/pip/_internal/models/wheel.py
     2673  2022-07-12 15:48   venv/pip/_internal/models/format_control.py
     1150  2022-07-12 15:48   venv/pip/_internal/models/candidate.py
     1908  2022-07-12 15:48   venv/pip/_internal/models/selection_prefs.py
     3898  2022-07-12 15:48   venv/pip/_internal/models/search_scope.py
     1146  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/index.cpython-38.pyc
     1596  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/selection_prefs.cpython-38.pyc
      224  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/__init__.cpython-38.pyc
     3182  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/wheel.cpython-38.pyc
     2416  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/format_control.cpython-38.pyc
     6659  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/link.cpython-38.pyc
     1421  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/candidate.cpython-38.pyc
     3253  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/search_scope.cpython-38.pyc
     3219  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/target_python.cpython-38.pyc
      862  2022-07-12 15:48   venv/pip/_internal/models/__pycache__/scheme.cpython-38.pyc
    10180  2022-07-12 15:48   venv/pip/_internal/operations/freeze.py
    20942  2022-07-12 15:48   venv/pip/_internal/operations/prepare.py
        0  2022-07-12 15:48   venv/pip/_internal/operations/__init__.py
     5353  2022-07-12 15:48   venv/pip/_internal/operations/check.py
     4566  2022-07-12 15:48   venv/pip/_internal/operations/install/legacy.py
       51  2022-07-12 15:48   venv/pip/_internal/operations/install/__init__.py
    23012  2022-07-12 15:48   venv/pip/_internal/operations/install/wheel.py
     1488  2022-07-12 15:48   venv/pip/_internal/operations/install/editable_legacy.py
      224  2022-07-12 15:48   venv/pip/_internal/operations/install/__pycache__/__init__.cpython-38.pyc
    14582  2022-07-12 15:48   venv/pip/_internal/operations/install/__pycache__/wheel.cpython-38.pyc
     3048  2022-07-12 15:48   venv/pip/_internal/operations/install/__pycache__/legacy.cpython-38.pyc
     1302  2022-07-12 15:48   venv/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-38.pyc
        0  2022-07-12 15:48   venv/pip/_internal/operations/build/__init__.py
     3349  2022-07-12 15:48   venv/pip/_internal/operations/build/wheel_legacy.py
     1469  2022-07-12 15:48   venv/pip/_internal/operations/build/wheel.py
     3957  2022-07-12 15:48   venv/pip/_internal/operations/build/metadata_legacy.py
     1307  2022-07-12 15:48   venv/pip/_internal/operations/build/metadata.py
      166  2022-07-12 15:48   venv/pip/_internal/operations/build/__pycache__/__init__.cpython-38.pyc
     1306  2022-07-12 15:48   venv/pip/_internal/operations/build/__pycache__/wheel.cpython-38.pyc
     2567  2022-07-12 15:48   venv/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-38.pyc
     1208  2022-07-12 15:48   venv/pip/_internal/operations/build/__pycache__/metadata.cpython-38.pyc
     3268  2022-07-12 15:48   venv/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-38.pyc
      160  2022-07-12 15:48   venv/pip/_internal/operations/__pycache__/__init__.cpython-38.pyc
     5803  2022-07-12 15:48   venv/pip/_internal/operations/__pycache__/freeze.cpython-38.pyc
     3656  2022-07-12 15:48   venv/pip/_internal/operations/__pycache__/check.cpython-38.pyc
    11162  2022-07-12 15:48   venv/pip/_internal/operations/__pycache__/prepare.cpython-38.pyc
     3481  2022-07-12 15:48   venv/pip/_internal/commands/freeze.py
     4209  2022-07-12 15:48   venv/pip/_internal/commands/debug.py
     3714  2022-07-12 15:48   venv/pip/_internal/commands/__init__.py
     7226  2022-07-12 15:48   venv/pip/_internal/commands/configuration.py
     5007  2022-07-12 15:48   venv/pip/_internal/commands/download.py
     2975  2022-07-12 15:48   venv/pip/_internal/commands/completion.py
     7170  2022-07-12 15:48   venv/pip/_internal/commands/wheel.py
    10660  2022-07-12 15:48   venv/pip/_internal/commands/list.py
     6792  2022-07-12 15:48   venv/pip/_internal/commands/show.py
    27286  2022-07-12 15:48   venv/pip/_internal/commands/install.py
     1735  2022-07-12 15:48   venv/pip/_internal/commands/hash.py
     2983  2022-07-12 15:48   venv/pip/_internal/commands/uninstall.py
     1505  2022-07-12 15:48   venv/pip/_internal/commands/check.py
     5148  2022-07-12 15:48   venv/pip/_internal/commands/search.py
     1181  2022-07-12 15:48   venv/pip/_internal/commands/help.py
     6325  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/show.cpython-38.pyc
     2852  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/__init__.cpython-38.pyc
     5246  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/wheel.cpython-38.pyc
     1990  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/hash.cpython-38.pyc
     2929  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/freeze.cpython-38.pyc
     4484  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/search.cpython-38.pyc
     4089  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/debug.cpython-38.pyc
     6575  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/configuration.cpython-38.pyc
     3013  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/completion.cpython-38.pyc
     1186  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/help.cpython-38.pyc
     2694  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/uninstall.cpython-38.pyc
     3921  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/download.cpython-38.pyc
     1312  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/check.cpython-38.pyc
     9042  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/list.cpython-38.pyc
    16661  2022-07-12 15:48   venv/pip/_internal/commands/__pycache__/install.cpython-38.pyc
      615  2022-07-12 15:48   venv/pip/_internal/__pycache__/main.cpython-38.pyc
     9898  2022-07-12 15:48   venv/pip/_internal/__pycache__/legacy_resolve.cpython-38.pyc
    12482  2022-07-12 15:48   venv/pip/_internal/__pycache__/exceptions.cpython-38.pyc
      678  2022-07-12 15:48   venv/pip/_internal/__pycache__/__init__.cpython-38.pyc
     3584  2022-07-12 15:48   venv/pip/_internal/__pycache__/pep425tags.cpython-38.pyc
    10645  2022-07-12 15:48   venv/pip/_internal/__pycache__/configuration.cpython-38.pyc
     8700  2022-07-12 15:48   venv/pip/_internal/__pycache__/cache.cpython-38.pyc
     6699  2022-07-12 15:48   venv/pip/_internal/__pycache__/wheel_builder.cpython-38.pyc
     5493  2022-07-12 15:48   venv/pip/_internal/__pycache__/self_outdated_check.cpython-38.pyc
     3732  2022-07-12 15:48   venv/pip/_internal/__pycache__/pyproject.cpython-38.pyc
     4492  2022-07-12 15:48   venv/pip/_internal/__pycache__/locations.cpython-38.pyc
     7483  2022-07-12 15:48   venv/pip/_internal/__pycache__/build_env.cpython-38.pyc
     2394  2022-07-12 15:48   venv/pip/_internal/network/cache.py
     1597  2022-07-12 15:48   venv/pip/_internal/network/xmlrpc.py
       50  2022-07-12 15:48   venv/pip/_internal/network/__init__.py
     1959  2022-07-12 15:48   venv/pip/_internal/network/utils.py
     6260  2022-07-12 15:48   venv/pip/_internal/network/download.py
    11119  2022-07-12 15:48   venv/pip/_internal/network/auth.py
    14702  2022-07-12 15:48   venv/pip/_internal/network/session.py
      706  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/utils.cpython-38.pyc
     8851  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/session.cpython-38.pyc
     1574  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/xmlrpc.cpython-38.pyc
      212  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/__init__.cpython-38.pyc
     6978  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/auth.cpython-38.pyc
     2691  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/cache.cpython-38.pyc
     4370  2022-07-12 15:48   venv/pip/_internal/network/__pycache__/download.cpython-38.pyc
    13033  2022-07-12 15:48   venv/pip/_internal/utils/logging.py
     3035  2022-07-12 15:48   venv/pip/_internal/utils/packaging.py
     3942  2022-07-12 15:48   venv/pip/_internal/utils/hashes.py
    13911  2022-07-12 15:48   venv/pip/_internal/utils/ui.py
     1254  2022-07-12 15:48   venv/pip/_internal/utils/pkg_resources.py
     1307  2022-07-12 15:48   venv/pip/_internal/utils/appdirs.py
      810  2022-07-12 15:48   venv/pip/_internal/utils/inject_securetransport.py
        0  2022-07-12 15:48   venv/pip/_internal/utils/__init__.py
     3297  2022-07-12 15:48   venv/pip/_internal/utils/glibc.py
     9438  2022-07-12 15:48   venv/pip/_internal/utils/unpacking.py
     1350  2022-07-12 15:48   venv/pip/_internal/utils/distutils_args.py
     1148  2022-07-12 15:48   venv/pip/_internal/utils/models.py
     7302  2022-07-12 15:48   venv/pip/_internal/utils/wheel.py
     1481  2022-07-12 15:48   venv/pip/_internal/utils/urls.py
     1401  2022-07-12 15:48   venv/pip/_internal/utils/typing.py
     1320  2022-07-12 15:48   venv/pip/_internal/utils/encoding.py
     5255  2022-07-12 15:48   venv/pip/_internal/utils/filesystem.py
     1152  2022-07-12 15:48   venv/pip/_internal/utils/entrypoints.py
     8869  2022-07-12 15:48   venv/pip/_internal/utils/compat.py
     7768  2022-07-12 15:48   venv/pip/_internal/utils/temp_dir.py
     9922  2022-07-12 15:48   venv/pip/_internal/utils/subprocess.py
     5070  2022-07-12 15:48   venv/pip/_internal/utils/setuptools_build.py
    26085  2022-07-12 15:48   venv/pip/_internal/utils/misc.py
      571  2022-07-12 15:48   venv/pip/_internal/utils/filetypes.py
      741  2022-07-12 15:48   venv/pip/_internal/utils/marker_files.py
     3318  2022-07-12 15:48   venv/pip/_internal/utils/deprecation.py
     3396  2022-07-12 15:48   venv/pip/_internal/utils/virtualenv.py
     6075  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/unpacking.cpython-38.pyc
     9159  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/logging.cpython-38.pyc
     1247  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/encoding.cpython-38.pyc
     6711  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/temp_dir.cpython-38.pyc
      932  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/inject_securetransport.cpython-38.pyc
     1708  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/glibc.cpython-38.pyc
      556  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/filetypes.cpython-38.pyc
     1299  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/entrypoints.cpython-38.pyc
      155  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/__init__.cpython-38.pyc
     6325  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/wheel.cpython-38.pyc
     1465  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/urls.cpython-38.pyc
     4035  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/filesystem.cpython-38.pyc
     2826  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/deprecation.cpython-38.pyc
     2927  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/setuptools_build.cpython-38.pyc
     3280  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/virtualenv.cpython-38.pyc
     2608  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/packaging.cpython-38.pyc
      928  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/marker_files.cpython-38.pyc
     1139  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/distutils_args.cpython-38.pyc
     1351  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/appdirs.cpython-38.pyc
     1924  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/models.cpython-38.pyc
     4144  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/hashes.cpython-38.pyc
    11802  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/ui.cpython-38.pyc
     5598  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/subprocess.cpython-38.pyc
    23778  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/misc.cpython-38.pyc
     1822  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/pkg_resources.cpython-38.pyc
     1437  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/typing.cpython-38.pyc
     6120  2022-07-12 15:48   venv/pip/_internal/utils/__pycache__/compat.cpython-38.pyc
      629  2022-07-12 15:48   venv/pip/__pycache__/__init__.cpython-38.pyc
      422  2022-07-12 15:48   venv/pip/__pycache__/__main__.cpython-38.pyc
   108349  2022-07-12 15:48   venv/pkg_resources/__init__.py
      558  2022-07-12 15:48   venv/pkg_resources/py31compat.py
    24701  2022-07-12 15:48   venv/pkg_resources/_vendor/appdirs.py
        0  2022-07-12 15:48   venv/pkg_resources/_vendor/__init__.py
    30098  2022-07-12 15:48   venv/pkg_resources/_vendor/six.py
   232055  2022-07-12 15:48   venv/pkg_resources/_vendor/pyparsing.py
      720  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__about__.py
     1416  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/_structures.py
      513  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__init__.py
     4355  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/requirements.py
      421  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/utils.py
     8248  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/markers.py
      860  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/_compat.py
    28025  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/specifiers.py
    11556  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/version.py
      466  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-38.pyc
     2763  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-38.pyc
     8919  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-38.pyc
    19787  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc
      707  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-38.pyc
      545  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-38.pyc
     3878  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-38.pyc
    10634  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/version.cpython-38.pyc
      981  2022-07-12 15:48   venv/pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-38.pyc
   201634  2022-07-12 15:48   venv/pkg_resources/_vendor/__pycache__/pyparsing.cpython-38.pyc
      157  2022-07-12 15:48   venv/pkg_resources/_vendor/__pycache__/__init__.cpython-38.pyc
    24430  2022-07-12 15:48   venv/pkg_resources/_vendor/__pycache__/six.cpython-38.pyc
    20510  2022-07-12 15:48   venv/pkg_resources/_vendor/__pycache__/appdirs.cpython-38.pyc
     2498  2022-07-12 15:48   venv/pkg_resources/extern/__init__.py
     2406  2022-07-12 15:48   venv/pkg_resources/extern/__pycache__/__init__.cpython-38.pyc
   100356  2022-07-12 15:48   venv/pkg_resources/__pycache__/__init__.cpython-38.pyc
      600  2022-07-12 15:48   venv/pkg_resources/__pycache__/py31compat.cpython-38.pyc
    48073  2022-07-12 15:50   venv/ops/framework.py
    88948  2022-07-12 15:50   venv/ops/pebble.py
    16783  2022-07-12 15:50   venv/ops/main.py
     2206  2022-07-12 15:50   venv/ops/__init__.py
    93183  2022-07-12 15:50   venv/ops/testing.py
    95925  2022-07-12 15:50   venv/ops/model.py
    41849  2022-07-12 15:50   venv/ops/charm.py
    14964  2022-07-12 15:50   venv/ops/storage.py
     4189  2022-07-12 15:50   venv/ops/jujuversion.py
        0  2022-07-12 15:50   venv/ops/py.typed
     2525  2022-07-12 15:50   venv/ops/log.py
       46  2022-07-12 15:50   venv/ops/version.py
      574  2022-07-12 15:50   venv/ops/_vendor/__init__.py
      147  2022-07-12 15:50   venv/ops/_vendor/__pycache__/__init__.cpython-38.pyc
    13142  2022-07-12 15:50   venv/ops/_vendor/websocket/_abnf.py
     1972  2022-07-12 15:50   venv/ops/_vendor/websocket/_logging.py
     2175  2022-07-12 15:50   venv/ops/_vendor/websocket/_exceptions.py
    11344  2022-07-12 15:50   venv/ops/_vendor/websocket/_http.py
      787  2022-07-12 15:50   venv/ops/_vendor/websocket/__init__.py
     5915  2022-07-12 15:50   venv/ops/_vendor/websocket/_handshake.py
     3524  2022-07-12 15:50   venv/ops/_vendor/websocket/_utils.py
     2135  2022-07-12 15:50   venv/ops/_vendor/websocket/_cookiejar.py
    15006  2022-07-12 15:50   venv/ops/_vendor/websocket/_app.py
    19358  2022-07-12 15:50   venv/ops/_vendor/websocket/_core.py
     4807  2022-07-12 15:50   venv/ops/_vendor/websocket/_url.py
     1337  2022-07-12 15:50   venv/ops/_vendor/websocket/_ssl_compat.py
     4843  2022-07-12 15:50   venv/ops/_vendor/websocket/_socket.py
     5160  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_handshake.cpython-38.pyc
     2412  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_exceptions.cpython-38.pyc
     4021  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_url.cpython-38.pyc
    11942  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_app.cpython-38.pyc
     1544  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_ssl_compat.cpython-38.pyc
      955  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/__init__.cpython-38.pyc
    17101  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_core.cpython-38.pyc
     2133  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_logging.cpython-38.pyc
     4119  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_utils.cpython-38.pyc
    10308  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_abnf.cpython-38.pyc
     1726  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_cookiejar.cpython-38.pyc
     7778  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_http.cpython-38.pyc
     3721  2022-07-12 15:50   venv/ops/_vendor/websocket/__pycache__/_socket.cpython-38.pyc
     9212  2022-07-12 15:50   venv/ops/lib/__init__.py
     7587  2022-07-12 15:50   venv/ops/lib/__pycache__/__init__.cpython-38.pyc
    11965  2022-07-12 15:50   venv/ops/__pycache__/main.cpython-38.pyc
    79087  2022-07-12 15:50   venv/ops/__pycache__/pebble.cpython-38.pyc
     1643  2022-07-12 15:50   venv/ops/__pycache__/__init__.cpython-38.pyc
    42989  2022-07-12 15:50   venv/ops/__pycache__/charm.cpython-38.pyc
     3556  2022-07-12 15:50   venv/ops/__pycache__/jujuversion.cpython-38.pyc
     2438  2022-07-12 15:50   venv/ops/__pycache__/log.cpython-38.pyc
    88982  2022-07-12 15:50   venv/ops/__pycache__/model.cpython-38.pyc
    71974  2022-07-12 15:50   venv/ops/__pycache__/testing.cpython-38.pyc
      155  2022-07-12 15:50   venv/ops/__pycache__/version.cpython-38.pyc
    41232  2022-07-12 15:50   venv/ops/__pycache__/framework.cpython-38.pyc
    13340  2022-07-12 15:50   venv/ops/__pycache__/storage.cpython-38.pyc
        0  2022-07-12 15:50   venv/ops/_private/__init__.py
     1104  2022-07-12 15:50   venv/ops/_private/yaml.py
      753  2022-07-12 15:50   venv/ops/_private/__pycache__/yaml.cpython-38.pyc
      148  2022-07-12 15:50   venv/ops/_private/__pycache__/__init__.cpython-38.pyc
    21518  2022-07-12 15:48   venv/pkg_resources-0.0.0.dist-info/AUTHORS.txt
      110  2022-07-12 15:48   venv/pkg_resources-0.0.0.dist-info/WHEEL
      177  2022-07-12 15:48   venv/pkg_resources-0.0.0.dist-info/METADATA
     1090  2022-07-12 15:48   venv/pkg_resources-0.0.0.dist-info/LICENSE.txt
     3100  2022-07-12 15:48   venv/pkg_resources-0.0.0.dist-info/RECORD
        4  2022-07-12 15:48   venv/pkg_resources-0.0.0.dist-info/INSTALLER
     1402  2022-07-12 15:50   venv/_yaml/__init__.py
      705  2022-07-12 15:50   venv/_yaml/__pycache__/__init__.cpython-38.pyc
       92  2022-07-12 15:50   venv/ops-1.5.0.dist-info/WHEEL
     7001  2022-07-12 15:50   venv/ops-1.5.0.dist-info/METADATA
    11358  2022-07-12 15:50   venv/ops-1.5.0.dist-info/LICENSE.txt
     4246  2022-07-12 15:50   venv/ops-1.5.0.dist-info/RECORD
        4  2022-07-12 15:50   venv/ops-1.5.0.dist-info/top_level.txt
        4  2022-07-12 15:50   venv/ops-1.5.0.dist-info/INSTALLER
      103  2022-07-12 15:50   venv/PyYAML-6.0.dist-info/WHEEL
     1101  2022-07-12 15:50   venv/PyYAML-6.0.dist-info/LICENSE
     2005  2022-07-12 15:50   venv/PyYAML-6.0.dist-info/METADATA
     2564  2022-07-12 15:50   venv/PyYAML-6.0.dist-info/RECORD
       11  2022-07-12 15:50   venv/PyYAML-6.0.dist-info/top_level.txt
        4  2022-07-12 15:50   venv/PyYAML-6.0.dist-info/INSTALLER
     1496  2022-07-12 15:48   venv/setuptools/py27compat.py
      218  2022-07-12 15:48   venv/setuptools/_deprecation_warning.py
     5264  2022-07-12 15:48   venv/setuptools/monkey.py
      218  2022-07-12 15:48   venv/setuptools/script (dev).tmpl
    65536  2022-07-12 15:48   venv/setuptools/cli.exe
     9597  2022-07-12 15:48   venv/setuptools/build_meta.py
      138  2022-07-12 15:48   venv/setuptools/script.tmpl
     7283  2022-07-12 15:48   venv/setuptools/__init__.py
     2302  2022-07-12 15:48   venv/setuptools/site-patch.py
     1330  2022-07-12 15:48   venv/setuptools/py33compat.py
     5517  2022-07-12 15:48   venv/setuptools/depends.py
      996  2022-07-12 15:48   venv/setuptools/unicode_utils.py
    75264  2022-07-12 15:48   venv/setuptools/gui-64.exe
      245  2022-07-12 15:48   venv/setuptools/py34compat.py
    65536  2022-07-12 15:48   venv/setuptools/gui.exe
    46751  2022-07-12 15:48   venv/setuptools/msvc.py
    65536  2022-07-12 15:48   venv/setuptools/gui-32.exe
      714  2022-07-12 15:48   venv/setuptools/windows_support.py
      838  2022-07-12 15:48   venv/setuptools/py31compat.py
    14276  2022-07-12 15:48   venv/setuptools/sandbox.py
     5337  2022-07-12 15:48   venv/setuptools/installer.py
     8455  2022-07-12 15:48   venv/setuptools/wheel.py
     1729  2022-07-12 15:48   venv/setuptools/extension.py
     6592  2022-07-12 15:48   venv/setuptools/archive_util.py
      524  2022-07-12 15:48   venv/setuptools/errors.py
     3199  2022-07-12 15:48   venv/setuptools/namespaces.py
      787  2022-07-12 15:48   venv/setuptools/launch.py
     8493  2022-07-12 15:48   venv/setuptools/ssl_support.py
    40605  2022-07-12 15:48   venv/setuptools/package_index.py
    49865  2022-07-12 15:48   venv/setuptools/dist.py
     5084  2022-07-12 15:48   venv/setuptools/glob.py
     2223  2022-07-12 15:48   venv/setuptools/_imp.py
    74752  2022-07-12 15:48   venv/setuptools/cli-64.exe
      935  2022-07-12 15:48   venv/setuptools/dep_util.py
    65536  2022-07-12 15:48   venv/setuptools/cli-32.exe
     2013  2022-07-12 15:48   venv/setuptools/lib2to3_ex.py
    20575  2022-07-12 15:48   venv/setuptools/config.py
      144  2022-07-12 15:48   venv/setuptools/version.py
        0  2022-07-12 15:48   venv/setuptools/_vendor/__init__.py
    30098  2022-07-12 15:48   venv/setuptools/_vendor/six.py
    15130  2022-07-12 15:48   venv/setuptools/_vendor/ordered_set.py
   232055  2022-07-12 15:48   venv/setuptools/_vendor/pyparsing.py
      744  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__about__.py
     1416  2022-07-12 15:48   venv/setuptools/_vendor/packaging/_structures.py
      562  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__init__.py
     4742  2022-07-12 15:48   venv/setuptools/_vendor/packaging/requirements.py
     1520  2022-07-12 15:48   venv/setuptools/_vendor/packaging/utils.py
     8268  2022-07-12 15:48   venv/setuptools/_vendor/packaging/markers.py
      865  2022-07-12 15:48   venv/setuptools/_vendor/packaging/_compat.py
    27778  2022-07-12 15:48   venv/setuptools/_vendor/packaging/specifiers.py
    12933  2022-07-12 15:48   venv/setuptools/_vendor/packaging/tags.py
    11978  2022-07-12 15:48   venv/setuptools/_vendor/packaging/version.py
     1435  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/utils.cpython-38.pyc
     2760  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/_structures.cpython-38.pyc
     8927  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/markers.cpython-38.pyc
    19734  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc
      704  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/__about__.cpython-38.pyc
      542  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/__init__.cpython-38.pyc
     3995  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/requirements.cpython-38.pyc
    10811  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/tags.cpython-38.pyc
    12065  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/version.cpython-38.pyc
      978  2022-07-12 15:48   venv/setuptools/_vendor/packaging/__pycache__/_compat.cpython-38.pyc
   201631  2022-07-12 15:48   venv/setuptools/_vendor/__pycache__/pyparsing.cpython-38.pyc
      154  2022-07-12 15:48   venv/setuptools/_vendor/__pycache__/__init__.cpython-38.pyc
    24427  2022-07-12 15:48   venv/setuptools/_vendor/__pycache__/six.cpython-38.pyc
    16412  2022-07-12 15:48   venv/setuptools/_vendor/__pycache__/ordered_set.cpython-38.pyc
     2439  2022-07-12 15:48   venv/setuptools/command/install_scripts.py
     1508  2022-07-12 15:48   venv/setuptools/command/bdist_rpm.py
    18185  2022-07-12 15:48   venv/setuptools/command/bdist_egg.py
     4484  2022-07-12 15:48   venv/setuptools/command/build_clib.py
     5023  2022-07-12 15:48   venv/setuptools/command/install_lib.py
      960  2022-07-12 15:48   venv/setuptools/command/dist_info.py
     4986  2022-07-12 15:48   venv/setuptools/command/py36compat.py
      568  2022-07-12 15:48   venv/setuptools/command/__init__.py
    25578  2022-07-12 15:48   venv/setuptools/command/egg_info.py
     8088  2022-07-12 15:48   venv/setuptools/command/sdist.py
     3195  2022-07-12 15:48   venv/setuptools/command/install_egg_info.py
     9602  2022-07-12 15:48   venv/setuptools/command/test.py
      628  2022-07-12 15:48   venv/setuptools/command/launcher manifest.xml
      637  2022-07-12 15:48   venv/setuptools/command/bdist_wininst.py
     2164  2022-07-12 15:48   venv/setuptools/command/rotate.py
    13019  2022-07-12 15:48   venv/setuptools/command/build_ext.py
     2426  2022-07-12 15:48   venv/setuptools/command/alias.py
    89903  2022-07-12 15:48   venv/setuptools/command/easy_install.py
     4705  2022-07-12 15:48   venv/setuptools/command/install.py
      468  2022-07-12 15:48   venv/setuptools/command/register.py
     8184  2022-07-12 15:48   venv/setuptools/command/develop.py
     9596  2022-07-12 15:48   venv/setuptools/command/build_py.py
      462  2022-07-12 15:48   venv/setuptools/command/upload.py
      658  2022-07-12 15:48   venv/setuptools/command/saveopts.py
     7311  2022-07-12 15:48   venv/setuptools/command/upload_docs.py
     5085  2022-07-12 15:48   venv/setuptools/command/setopt.py
    21773  2022-07-12 15:48   venv/setuptools/command/__pycache__/egg_info.cpython-38.pyc
     5083  2022-07-12 15:48   venv/setuptools/command/__pycache__/install_lib.cpython-38.pyc
     1361  2022-07-12 15:48   venv/setuptools/command/__pycache__/dist_info.cpython-38.pyc
      954  2022-07-12 15:48   venv/setuptools/command/__pycache__/bdist_wininst.cpython-38.pyc
     1784  2022-07-12 15:48   venv/setuptools/command/__pycache__/bdist_rpm.cpython-38.pyc
     2520  2022-07-12 15:48   venv/setuptools/command/__pycache__/rotate.cpython-38.pyc
      712  2022-07-12 15:48   venv/setuptools/command/__pycache__/__init__.cpython-38.pyc
      889  2022-07-12 15:48   venv/setuptools/command/__pycache__/saveopts.cpython-38.pyc
     2437  2022-07-12 15:48   venv/setuptools/command/__pycache__/build_clib.cpython-38.pyc
     9895  2022-07-12 15:48   venv/setuptools/command/__pycache__/build_ext.cpython-38.pyc
     8475  2022-07-12 15:48   venv/setuptools/command/__pycache__/test.cpython-38.pyc
     6501  2022-07-12 15:48   venv/setuptools/command/__pycache__/develop.cpython-38.pyc
     2273  2022-07-12 15:48   venv/setuptools/command/__pycache__/install_scripts.cpython-38.pyc
     2392  2022-07-12 15:48   venv/setuptools/command/__pycache__/alias.cpython-38.pyc
     4537  2022-07-12 15:48   venv/setuptools/command/__pycache__/setopt.cpython-38.pyc
     8641  2022-07-12 15:48   venv/setuptools/command/__pycache__/build_py.cpython-38.pyc
    66694  2022-07-12 15:48   venv/setuptools/command/__pycache__/easy_install.cpython-38.pyc
     7859  2022-07-12 15:48   venv/setuptools/command/__pycache__/sdist.cpython-38.pyc
      811  2022-07-12 15:48   venv/setuptools/command/__pycache__/register.cpython-38.pyc
     2900  2022-07-12 15:48   venv/setuptools/command/__pycache__/install_egg_info.cpython-38.pyc
      784  2022-07-12 15:48   venv/setuptools/command/__pycache__/upload.cpython-38.pyc
    14182  2022-07-12 15:48   venv/setuptools/command/__pycache__/bdist_egg.cpython-38.pyc
     6140  2022-07-12 15:48   venv/setuptools/command/__pycache__/upload_docs.cpython-38.pyc
     4016  2022-07-12 15:48   venv/setuptools/command/__pycache__/install.cpython-38.pyc
     4610  2022-07-12 15:48   venv/setuptools/command/__pycache__/py36compat.cpython-38.pyc
     2514  2022-07-12 15:48   venv/setuptools/extern/__init__.py
     2420  2022-07-12 15:48   venv/setuptools/extern/__pycache__/__init__.cpython-38.pyc
     3616  2022-07-12 15:48   venv/setuptools/__pycache__/namespaces.cpython-38.pyc
      816  2022-07-12 15:48   venv/setuptools/__pycache__/errors.cpython-38.pyc
     5128  2022-07-12 15:48   venv/setuptools/__pycache__/archive_util.cpython-38.pyc
      989  2022-07-12 15:48   venv/setuptools/__pycache__/windows_support.cpython-38.pyc
     5216  2022-07-12 15:48   venv/setuptools/__pycache__/depends.cpython-38.pyc
    39631  2022-07-12 15:48   venv/setuptools/__pycache__/msvc.cpython-38.pyc
      819  2022-07-12 15:48   venv/setuptools/__pycache__/dep_util.cpython-38.pyc
    15536  2022-07-12 15:48   venv/setuptools/__pycache__/sandbox.cpython-38.pyc
      824  2022-07-12 15:48   venv/setuptools/__pycache__/launch.cpython-38.pyc
     7768  2022-07-12 15:48   venv/setuptools/__pycache__/__init__.cpython-38.pyc
     7383  2022-07-12 15:48   venv/setuptools/__pycache__/wheel.cpython-38.pyc
     1408  2022-07-12 15:48   venv/setuptools/__pycache__/py33compat.cpython-38.pyc
     1961  2022-07-12 15:48   venv/setuptools/__pycache__/extension.cpython-38.pyc
      450  2022-07-12 15:48   venv/setuptools/__pycache__/py34compat.cpython-38.pyc
     8495  2022-07-12 15:48   venv/setuptools/__pycache__/build_meta.cpython-38.pyc
     1474  2022-07-12 15:48   venv/setuptools/__pycache__/site-patch.cpython-38.pyc
     1749  2022-07-12 15:48   venv/setuptools/__pycache__/py27compat.cpython-38.pyc
     3733  2022-07-12 15:48   venv/setuptools/__pycache__/glob.cpython-38.pyc
     1890  2022-07-12 15:48   venv/setuptools/__pycache__/_imp.cpython-38.pyc
      292  2022-07-12 15:48   venv/setuptools/__pycache__/version.cpython-38.pyc
     6855  2022-07-12 15:48   venv/setuptools/__pycache__/ssl_support.cpython-38.pyc
    42326  2022-07-12 15:48   venv/setuptools/__pycache__/dist.cpython-38.pyc
     1151  2022-07-12 15:48   venv/setuptools/__pycache__/unicode_utils.cpython-38.pyc
    17896  2022-07-12 15:48   venv/setuptools/__pycache__/config.cpython-38.pyc
     4102  2022-07-12 15:48   venv/setuptools/__pycache__/installer.cpython-38.pyc
     4644  2022-07-12 15:48   venv/setuptools/__pycache__/monkey.cpython-38.pyc
     1191  2022-07-12 15:48   venv/setuptools/__pycache__/py31compat.cpython-38.pyc
     2411  2022-07-12 15:48   venv/setuptools/__pycache__/lib2to3_ex.cpython-38.pyc
    32970  2022-07-12 15:48   venv/setuptools/__pycache__/package_index.cpython-38.pyc
      516  2022-07-12 15:48   venv/setuptools/__pycache__/_deprecation_warning.cpython-38.pyc
      281  2022-07-12 15:48   venv/__pycache__/easy_install.cpython-38.pyc
      447  2022-07-12 15:44   src/charm.py
---------                     -------
  5607942                     558 files

Congratulations, your Kubernetes charm is ready!

Deploy your charm

You now have a Kubernetes charm. Time to check that it’s functional by deploying it!

First, you’ll need to prepare your Kubernetes cloud. Use MicroK8s to create and configure a small Kubernetes cloud on your local workstation, as shown below. This cloud is by default called microk8s.

# Install Microk8s from snap:
sudo snap install microk8s --classic --channel=1.24

# Add the 'ubuntu' user to the Microk8s group:
sudo usermod -a -G microk8s ubuntu

# Give the 'ubuntu' user permissions to read the ~/.kube directory:
sudo chown -f -R ubuntu ~/.kube

# Create the 'microk8s' group:
newgrp microk8s

# Enable the necessary Microk8s addons:
microk8s enable hostpath-storage dns

Second, you’ll need to set up the charmed operator lifecycle manager (OLM) juju, make it aware of your cloud, connect it to your cloud, and prepare a workspace for your cloud. You can achieve all of this in a few lines with the code below:

#Install the Juju CLI client, juju:
$ sudo snap install juju --classic
juju 2.9.32 from Canonicalâś“ installed

# Register your "microk8s" cloud with juju. Not necessary, as juju recognises a MicroK8s cloud automatically, as you can see by running `juju clouds`. (If for any reason this doesn't happen, you can register it manually using `juju add-k8s microk8s`.)
$ juju clouds
Cloud      Regions  Default    Type  Credentials  Source    Description
microk8s   1        localhost  k8s   1            built-in  A Kubernetes Cluster

# Install a "juju" controller into your "microk8s" cloud. We'll name ours "tutorial-controller".
$ juju bootstrap microk8s tutorial-controller

# Create a workspace, or 'model', on this controller. We'll call ours "tutorial-model".
$ juju add-model tutorial-model

Well done! Everything is now ready for you to deploy your charm!

Before you jump to it, though, you might want to catch any errors as they happen. To that end, switch to a different terminal, open a new shell into your Multipass virtual machine where you installed Juju, and execute the following to see the logging, as shown below:

# Open a shell into the virtual machine that you created earlier, 'tutorial-vm':
$ multipass shell tutorial-vm
ubuntu@tutorial-vm:~$ juju debug-log
controller-0: 22:37:02 INFO juju.worker.apicaller [48d45f] "controller-0" successfully connected to "localhost:17070"
controller-0: 22:37:02 INFO juju.worker.logforwarder config change - log forwarding not enabled
controller-0: 22:37:02 INFO juju.worker.logger logger worker started
controller-0: 22:37:02 INFO juju.worker.pruner.statushistory status history config: max age: 336h0m0s, max collection size 5120M for hello-world (48d45fd3-bc24-467e-8f88-2378dda24211)
controller-0: 22:37:02 INFO juju.worker.pruner.action status history config: max age: 336h0m0s, max collection size 5120M for hello-world (48d45fd3-bc24-467e-8f88-2378dda24211)

Switch back to the original terminal, and let’s deploy our charm:

$ juju deploy ./hello-world_ubuntu-20.04-amd64.charm

You can watch the evolving status of the deployment with:

$ watch --color "juju status --color"

Wait a few moments to allow the Juju OLM to do its magic. If everything has gone successfully, the juju debug-log terminal should soon display a line very similar to the one below:

unit-hello-world-1: 13:26:10 INFO unit.hello-world/1.juju-log Congratulations, the charm was properly installed!

Destroy the test environment

Once you are done, you can run the code below to stop and delete your Multipass test environment.

# Stop your instance
$ multipass stop tutorial-vm

# Delete your instance permanently
$ multipass delete --purge tutorial-vm

You can also uninstall Multipass to remove any trace of this guide.

Next steps

This tutorial has introduced you to the basic process for building a Kubernetes charm in Juju. But there is a lot more to explore:

If you are wondering… visit…
“How do I…?” Juju SDK How-to docs
“What is…?” Juju SDK Reference docs
“Why…?”, “So what?” Juju SDK Explanation docs

Finally, if your question is “How can I learn more about deploying charms with Juju?”, consider also the Juju OLM docs!

I gave this a read through, and a try. Overall, it was clearly written, and worked well. I do have a few notes, though :slight_smile:

  • tree and unzip aren’t included in the default multipass image. You need to do sudo apt update && sudo apt install tree unzip in order to get them.

  • The default editor in the multipass image is vim. So edit src/charm.py opens a vim window. This might be a little confusing for someone used to an IDE like PyCharm. I prefer emacs, but can use vim. I didn’t know the shortcut to clear all text in a buffer off the top of my head, and just ended up just doing echo "" > src/charm.py.

    It might be useful to include some notes on opening files in a multipass vm in your favorite editor or IDE.

  • The use of “series” is deprecated. You can just omit that line in metadata.yaml (Edit: though this may not actually work – just looped back to my multipass vm, and noticed that the charm isn’t actually deploying.)

  • charmcraft pack won’t work on a fresh vm. You need to procede it with lxd init --auto

  • On an ARM64 Box, like an M1 Mac, the name of the .charm file will be different. You could get around this discrepancy by just replacing the base with a *: juju deploy ./hello-world*.charm

  • The below watch command gives a much friendlier and more colorful display on a fresh vm:

    watch --color "juju status --color"

1 Like

@jameinel: what is the current best practice when it comes to deploying a workload-less sidecar charm, as this tutorial attempts to do?

Including “series” works in the metadata.yaml works, but series is deprecated. If I exclude series, deploy hangs. This is also the case if I add an empty containers directive.

1 Like

Is that still true if you have a “build-on” in the charmcraft.yaml? (we do need to know what series the charm is running on, though we should be getting it from charmcraft populating the manifest.yaml instead of the series in metadata.yaml.) That said, we could easily have a bug here where we still depend on a series: entry.

1 Like

Thanks for the detailed feedback, @pengale! I’ve updated the Multipass section with tree and unzip, and the Charmcraft section with lxd (although that bit does kinda come out of the blue; mentioning in case you can think of a better place where to put it). Also updated the watch command

About the name of the charm file: So long as people work inside Multipass, the name should always be the same, and based on Ubuntu, right?

About the editor inside Multipass: I ran into the same issue and ended up installing Emacs :blush: but I agree we should do better. It’s little things like this that confuse and discourage a beginner.

@tmihoc the name will vary depending on the architecture. It’s “arm64” rather than “amd64” on my Macbook M1, because the underlying processor is an ARM processor.

1 Like

I’ve just run through the tutorial and the deploy keeps hanging saying “installing agent” under the app message.

After reading this, I figured it was related [so I looked through the post history] and added series: [kubernetes] back into the metadata.yaml. It seemed to make it a little further, but it still hangs.

Model           Controller           Cloud/Region        Version  SLA          Timestamp
tutorial-model  tutorial-controller  microk8s/localhost  2.9.38   unsupported  15:34:19-04:00

App          Version  Status   Scale  Charm        Channel  Rev  Address  Exposed  Message
hello-world           unknown      1  hello-world             0           no

Unit            Workload  Agent  Address  Ports  Message
hello-world/0*  unknown   idle

I’d give this a go, but I’m not sure what the proper build-on section would look like.

I spent quite a bit of time trying to debug what was going on with my deploy, but ultimately I couldn’t find anything broken. I tried with fresh clean installs multiple times and nothing I did changed the end state of “installing agent”. Namely, I tried pinning charmcraft to a version that would have been available at the time this tutorial.

That being said, I did notice that the desired log (Congratulations, the charm was properly installed!) did actually appear in juju debug-log, so I guess that counts as successful.

Is the perpetual “installing agent” message expected here? It seems a little misleading and I feel like something isn’t working as expected, even if the tutorial can be successfully completed.