Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add "How to manage 12-factor app charms" #2048

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ docs: ## Build documentation

.PHONY: docs-auto
docs-auto: ## Build and host docs with sphinx-autobuild
uv run --extra docs sphinx-autobuild -b html --open-browser --port=8080 --ignore *.kate-swp --ignore docs/reference/commands/** --watch $(PROJECT) $(DOCS) $(DOCS)/_build
uv run --extra docs sphinx-autobuild -b html --open-browser --port=8080 --ignore *.kate-swp --ignore 'docs/reference/commands/**' --watch $(PROJECT) $(DOCS) $(DOCS)/_build

.PHONY: pack-pip
pack-pip: ##- Build packages for pip (sdist, wheel)
Expand Down
1 change: 1 addition & 0 deletions docs/howto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ How-To
.. toctree::
:maxdepth: 2

manage-12-factor-app-charms
charm-to-poetry
charm-to-python
shared-cache
342 changes: 342 additions & 0 deletions docs/howto/manage-12-factor-app-charms.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
.. _howto-manage-12-factor-app-charms

Manage 12-factor app charms
***************************

.. raw:: html

<!--TODO: There are a few more low-hanging fruit topics that we can and should cover:

When we create such sections we should include thinks at both the rock and the charm level (because the 12-Factor app app charm wants you to think about them in this coupled fashion). However, for the Rockcraft things, we should have those sections point to Rockcraft docs. For example:

## Update the OCI image for a 12-Factor app charm

See {ref}`Rockcraft Documentation > How to update the OCI image... <15018md>`

-->
Comment on lines +6 to +16
Copy link
Contributor

@medubelko medubelko Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use Sphinx comments to comment out the whole block.

I like that you've left it as Markdown, so we know it's not finished.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just left as Markdown, it's left as the raw pandoc output 🙂


.. card:: See also
:link: https://juju.is/docs/juju/charmed-operator

``juju`` | 12-factor app charms
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not jazzed by using cards, for several reasons. I think we can make do by making this a note or a plain paragraph, which is closer to the original structure (blockquote). That way we won't need to introduce new patterns and directives.

Longer-term I'd like to come up with smoother wording for introducing these links. Dressing them up as notes will be a good way to encourage me to correct them, since you know I don't like notes. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I revisited this question with Dora. Let's instead use block quotes (two prepended spaces) for these.

That's also a structure we don't use often, so I'll easily be able to pick it up in later reviews/rewrites.


Prepare an OCI image for a 12-factor app charm
----------------------------------------------

.. card:: See more
:link: https://documentation.ubuntu.com/rockcraft/en/latest/how-to/build-a-12-factor-app-rock

``rockcraft`` | How to build a 12-factor app

Initialise a 12-factor app charm
--------------------------------

Use ``charmcraft init`` and specify the relevant profile:

.. tabs::
:sync-group: framework

.. group-tab:: Flask

.. code:: text

charmcraft init --profile flask-framework

.. group-tab:: Django

.. code:: text

charmcraft init --profile django-framework

.. group-tab:: FastAPI

.. code:: text

charmcraft init --profile fastapi-framework

.. group-tab:: Go

.. code:: text

charmcraft init --profile go-framework

Charmcraft automatically creates ``charmcraft.yaml``,
``requirements.txt`` and source code for the charm in your current
directory. You will need to check ``charmcraft.yaml`` and ``README.md``
and verify that the charm’s name and description are correct.

.. card:: See also:
:link-type: ref
:link: ref_commands_init

``init`` command


Manage configurations for a 12-factor app charm
-----------------------------------------------

A charm configuration can be added if your 12-factor app requires
environment variables, for example, to pass a token for a service. Add
the configuration in ``charmcraft.yaml``:

.. code:: text
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use yaml?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just missed this one in the pandoc output.

Suggested change
.. code:: text
.. code:: yaml


config:
options:
token:
description: The token for the service.
type: string

.. tabs::
:sync-group: framework

.. group-tab:: Flask

A user-defined configuration option will correspond to an environment
variable generated by the ``paas-charm`` project to expose the
configuration to the Flask workload. In general, a configuration option
``config-option-name`` will be mapped as ``FLASK_CONFIG_OPTION_NAME``
where the option name will be converted to upper case, dashes will be
converted to underscores and the ``FLASK_`` prefix will be added. In the
example above, the ``token`` configuration will be mapped as the
``FLASK_TOKEN`` environment variable. In addition to the environment
variable, the configuration is also available in the Flask variable
``app.config`` without the ``FLASK_`` prefix.

The configuration can be set on the deployed charm using
``juju config <app name> token=<token>``.

.. card-carousel:: 2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof, I'm very strongly not in favour of how this looks, either.


.. card:: See also
:link: https://juju.is/docs/sdk/config

How to add configuration to a charm

.. card:: See also
:link: https://flask.palletsprojects.com/en/3.0.x/config/

Configuration Handling – Flask Documentation

.. group-tab:: Django

A user-defined configuration option will correspond to an environment variable generated by the ``paas-charm`` project to expose the configuration to the Django workload. In general, a configuration option ``config-option-name`` will be mapped as ``DJANGO_CONFIG_OPTION_NAME`` where the option name will be converted to upper case, dashes will be converted to underscores and the ``DJANGO_`` prefix will be added. In the example above, the ``token`` configuration will be mapped as the ``DJANGO_TOKEN`` environment variable.

The configuration can be set on the deployed charm using ``juju config <app name> token=<token>``.

.. card-carousel:: 2

.. card:: See also
:link: https://juju.is/docs/sdk/config

How to add configuration to a charm

.. card:: See also
:link: https://docs.djangoproject.com/en/stable/topics/settings/

Django settings | Django documentation

.. group-tab:: FastAPI

A user-defined configuration option will correspond to an environment
variable generated by the ``paas-charm`` project to expose the
configuration to the FastAPI workload. In general, a configuration
option ``config-option-name`` will be mapped as
``APP_CONFIG_OPTION_NAME`` where the option name will be converted to
upper case, dashes will be converted to underscores and the ``APP_``
prefix will be added. In the example above, the ``token`` configuration
will be mapped as the ``APP_TOKEN`` environment variable.

The configuration can be set on the deployed charm using
``juju config <app name> token=<token>``.

.. card-carousel:: 2

.. card:: See also
:link: https://juju.is/docs/sdk/config

How to add configuration to a charm

.. card:: See also
:link: https://fastapi.tiangolo.com/advanced/settings/

Settings and Environment Variables - FastAPI

.. group-tab:: Go

A user-defined configuration option will correspond to an environment variable generated by the ``paas-charm`` project to expose the configuration to the Go workload. In general, a configuration option ``config-option-name`` will be mapped as ``APP_CONFIG_OPTION_NAME`` where the option name will be converted to upper case, dashes will be converted to underscores and the ``APP_`` prefix will be added. In the example above, the `token`` configuration will be mapped as the ``APP_TOKEN`` environment variable.

The configuration can be set on the deployed charm using ``juju config <app name> token=<token>``.

.. card-carousel:: 2

.. card:: See also
:link: https://juju.is/docs/sdk/config

How to add configuration to a charm


Manage relations for a 12-factor app charm
------------------------------------------

A charm integration can be added to your charmed 12-factor app by
providing the integration and endpoint definition in
``charmcraft.yaml``:

.. code:: yaml

requires:
<endpoint name>:
interface: <endpoint interface name>
optional: false

Here, ``<endpoint name>`` corresponds to the endpoint of the application
with which you want the integration, and ``<endpoint interface name>``
is the endpoint schema to which this relation conforms. Both the
``<endpoint name>`` and ``<endpoint interface name>`` must coincide with
the structs defined in that particular application’s charm’s
``charmcraft.yaml`` file. The key ``optional`` with value ``False``
means that the charm will get blocked and stop the services if the
integration is not provided.

You can provide the integration to your deployed 12-factor app using
``juju integrate <12-factor app charm> <endpoint name>``. After the
integration has been established, the connection string and other
configuration options will be available as environment variables that
you may use to configure your 12-factor application.

For example, if you wish to integrate your 12-factor application with
PostgreSQL (`machine <https://charmhub.io/postgresql>`__ or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like Pandoc added extra underscores to links.

`k8s <https://charmhub.io/postgresql-k8s>`__ charm), add the following
endpoint definition to ``charmcraft.yaml``:

.. code:: yaml

requires:
postgresql:
interface: postgresql_client
optional: True

Provide the integration to your deployed 12-factor app with
``juju integrate <12-factor app charm> postgresql``. This integration
creates the following environment variables you may use to configure
your 12-factor application:

- ``POSTGRESQL_DB_CONNECT_STRING``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also looks like it added two spaces after list iterators.

- ``POSTGRESQL_DB_SCHEME``
- ``POSTGRESQL_DB_NETLOC``
- ``POSTGRESQL_DB_PATH``
- ``POSTGRESQL_DB_PARAMS``
- ``POSTGRESQL_DB_QUERY``
- ``POSTGRESQL_DB_FRAGMENT``
- ``POSTGRESQL_DB_USERNAME``
- ``POSTGRESQL_DB_PASSWORD``
- ``POSTGRESQL_DB_HOSTNAME``
- ``POSTGRESQL_DB_PORT``

.. card:: See also
:link: https://juju.is/docs/sdk/implement-integrations-in-a-charm

How to add an integration to a charm

Manage secrets for a 12-factor app charm
----------------------------------------

A user secret can be added to a charm and all the keys and values in the
secret will be exposed as environment variables. Add the secret
configuration option in charmcraft.yaml:

.. code:: yaml

config:
options:
api-token:
type: secret
description: Secret needed to access some API secret information

Once the charm is deployed, you can add a Juju secret to the model::

juju add-secret my-api-token value=1234 othervalue=5678
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no rule about this, but for consistency I think it would be better to use the code directive for blocks, unless it's code imported from a literalinclude.


The output from the previous command will look something like::

secret:cru00lvmp25c77qa0qrg

From the output of the previous command, you can get the Juju secret ID.
Grant the application access to view the value of the secret::

juju grant-secret my-api-token <app name>

Add the Juju secret ID to the application::

juju config <app name> api-token=secret:cru00lvmp25c77qa0qrg

.. tabs::
:sync-group: framework

.. group-tab:: Flask

The following environment variables are available for the application:

- ``FLASK_API_TOKEN_VALUE``: ``"1234"``
- ``FLASK_API_TOKEN_OTHERVALUE``: ``"5678"``

.. group-tab:: Django

The following environment variables are available for the application:

- ``DJANGO_API_TOKEN_VALUE``: ``"1234"``
- ``DJANGO_API_TOKEN_OTHERVALUE``: ``"5678"``

.. group-tab:: FastAPI

The following environment variables are available for the application:

- ``APP_API_TOKEN_VALUE``: ``"1234"``
- ``APP_API_TOKEN_OTHERVALUE``: ``"5678"``

.. group-tab:: Go

The following environment variables are available for the application:

- ``APP_API_TOKEN_VALUE``: ``"1234"``
- ``APP_API_TOKEN_OTHERVALUE``: ``"5678"``

.. card:: See also
:link: https://juju.is/docs/sdk/add-a-secret-to-a-charm

How to manage secrets

Use 12-factor app charms
------------------------

.. tabs::
:sync-group: framework

.. group-tab:: Flask

.. group-tab:: Django

Use the ``create-superuser`` action to create a new Django admin account::

juju run <app name> create-superuser username=<username> email=<email>

You must provide the username and email address.

.. group-tab:: FastAPI

.. group-tab:: Go
Comment on lines +313 to +328
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution to the empty tags is to modify both the content and structure. If this action only applies to Django, then let's either:

  • include "Django" in the heading, or
  • Start the section with a paragraph that states Django is the only framework with this capability.


(If your workload depends on a database) Migrate the database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your app depends on a database, it is common to run a database migration script
before app startup which, for example, creates or modifies tables. This can be done
by including the ``migrate.sh`` script in the root of your project. It will be
executed with the same environment variables and context as the 12-factor app.

If the migration script fails, it will retry upon ``update-status``. The migration
script will run on every unit. The script is assumed to be idempotent (i.e., can
be run multiple times) and that it can be run on multiple units simultaneously
without issue. Handling multiple migration scripts that run concurrently can be
achieved by, for example, locking any tables during the migration.
Loading