From 414db2b77e89a122e7527ed48f3e1b3b7f8803eb Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 17 Dec 2024 12:40:02 -0500 Subject: [PATCH] docs: add "How to manage 12-factor app charms" I'm breaking down the content from PR #2010 into smaller chunks. This is "How to manage 12-factor app charms" converted to RST: https://github.com/canonical/charmcraft/blob/737976c943a601adccd883ffa6a276bce625c9a1/docs/howto/manage-12-factor-app-charms.md The main differences from the Markdown version: - Uses tabs instead of dropdowns (matching the Rockcraft docs: https://documentation.ubuntu.com/rockcraft/en/latest/how-to/build-a-12-factor-app-rock/) - "See also" blocks are added as cards --- common.mk | 2 +- docs/howto/index.rst | 1 + docs/howto/manage-12-factor-app-charms.rst | 335 +++++++++++++++++++++ 3 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 docs/howto/manage-12-factor-app-charms.rst diff --git a/common.mk b/common.mk index 4f5c9ef49..fafb941cc 100644 --- a/common.mk +++ b/common.mk @@ -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) diff --git a/docs/howto/index.rst b/docs/howto/index.rst index 9bf000898..0eeeb098a 100644 --- a/docs/howto/index.rst +++ b/docs/howto/index.rst @@ -6,6 +6,7 @@ How-To .. toctree:: :maxdepth: 2 + manage-12-factor-app-charms charm-to-poetry charm-to-python shared-cache diff --git a/docs/howto/manage-12-factor-app-charms.rst b/docs/howto/manage-12-factor-app-charms.rst new file mode 100644 index 000000000..8d67cf6ab --- /dev/null +++ b/docs/howto/manage-12-factor-app-charms.rst @@ -0,0 +1,335 @@ +.. _howto-manage-12-factor-app-charms + +How to manage 12-factor app charms +********************************** + +.. raw:: html + + + +.. card:: See also + :link: https://juju.is/docs/juju/charmed-operator + + ``juju`` | 12-factor app charms + +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 + + 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 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://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 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 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 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: + : + interface: + optional: false + +Here, ```` corresponds to the endpoint of the application +with which you want the integration, and ```` +is the endpoint schema to which this relation conforms. Both the +```` and ```` 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> ``. 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 `__ or +`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`` +- ``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 + +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 + +Add the Juju secret ID to the application:: + + juju config 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 create-superuser username= email= + + You must provide the username and email address. + + .. group-tab:: FastAPI + + .. group-tab:: Go + +(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.