diff --git a/docs/custom_conf.py b/docs/custom_conf.py index 19ae79fd5..109c2ed8f 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -59,9 +59,9 @@ # FIXME: temporarily disable missing xrefs in this branch, until the missing # links to Juju and Charmcraft are added. -suppress_warnings = [ - 'myst.xref_missing', -] +#suppress_warnings = [ +# 'myst.xref_missing', +#] ############################################################ ### Project information diff --git a/docs/reference/charm-relation-interfaces.md b/docs/explanation/charm-relation-interfaces.md similarity index 100% rename from docs/reference/charm-relation-interfaces.md rename to docs/explanation/charm-relation-interfaces.md diff --git a/docs/explanation/index.md b/docs/explanation/index.md index 0238e87d8..23826e84a 100644 --- a/docs/explanation/index.md +++ b/docs/explanation/index.md @@ -4,6 +4,9 @@ ```{toctree} :maxdepth: 1 +charm-relation-interfaces +testing +interface-tests holistic-vs-delta-charms how-and-when-to-defer-events storedstate-uses-limitations diff --git a/docs/reference/interface-tests.md b/docs/explanation/interface-tests.md similarity index 100% rename from docs/reference/interface-tests.md rename to docs/explanation/interface-tests.md diff --git a/docs/reference/testing.md b/docs/explanation/testing.md similarity index 77% rename from docs/reference/testing.md rename to docs/explanation/testing.md index 0ba3df7b1..0e2dbb8b6 100644 --- a/docs/reference/testing.md +++ b/docs/explanation/testing.md @@ -83,6 +83,28 @@ When writing an integration test, it is not sufficient to simply check that Juju - [pytest-operator](https://github.com/charmed-kubernetes/pytest-operator) and/or [`zaza`](https://github.com/openstack-charmers/zaza) +(pytest-operator)= +### `pytest-operator` + +`pytest-operator` is a Python library that provides Juju plugins for the generic Python library `pytest` to facilitate the {ref}`integration testing ` of charms. + +> See more: [`pytest-operator`](https://github.com/charmed-kubernetes/pytest-operator) + +It builds a fixture called `ops_test` that helps you interact with Juju through constructs that wrap around [`python-libjuju` ](https://pypi.org/project/juju/). + +> See more: +> - [`pytest-operator` > `ops_test`](https://github.com/charmed-kubernetes/pytest-operator/blob/main/docs/reference.md#ops_test) +> - [`pytest` > Fixtures](https://docs.pytest.org/en/6.2.x/fixture.html) + +It also provides convenient markers and command line parameters (e.g., the `@pytest.mark.skip_if_deployed` marker in combination with the `--no-deploy` configuration helps you skip, e.g., a deployment test in the case where you already have a deployment). + + +> See more: +> - [`pytest-operator` > Markers](https://github.com/charmed-kubernetes/pytest-operator/blob/main/docs/reference.md#markers) +> - [`pytest-operator` > Command line parameters](https://github.com/charmed-kubernetes/pytest-operator/blob/main/docs/reference.md#command-line-parameters) + + + **Examples.** - [https://github.com/canonical/prometheus-k8s-operator/blob/main/tests/integration/test_charm.py](https://github.com/canonical/prometheus-k8s-operator/blob/main/tests/integration/test_charm.py) diff --git a/docs/howto/get-started-with-charm-testing.md b/docs/howto/get-started-with-charm-testing.md index 0c2edf63f..7172d5749 100644 --- a/docs/howto/get-started-with-charm-testing.md +++ b/docs/howto/get-started-with-charm-testing.md @@ -6,7 +6,7 @@ Testing charm code is an essential part of charming. Here we will see how to get **What you'll need:** - knowledge of testing in general - knowledge of Juju and charms -- knowledge of the Juju models and events, esp. the data involved in a charm's lifecycle (e.g. see [Talking to a workload control flow from A to Z]()) +- knowledge of the Juju models and events, esp. the data involved in a charm's lifecycle **What you will learn:** - What are the starting points for adding tests to a charm? @@ -77,7 +77,9 @@ If the charm is a machine charm, workload operation calls can be done directly, "Juju operations" are the most 'meta' of them all: they do not affect the workload in and of itself, but they share data which is meant to affect the operation of *other* charms that this charm is integrated with. + ### What we are testing when we unit-test @@ -120,7 +122,9 @@ There are two ways to initialize a harnessed charm: * When a charm is deployed, it goes through the Setup phase, a fixed sequence of events. `Harness` has a method, `begin_with_initial_hooks()`, that runs this sequence. * Alternatively, you can initialise the charm by calling `begin()`. This will instantiate the charm without firing any Setup phase event. + After the Setup phase, the charm goes into Operation. To test operation-phase-related events, the harness provides some methods to simulate the most common scenarios. For example: diff --git a/docs/howto/index.md b/docs/howto/index.md index 8fec3f173..8269419a2 100644 --- a/docs/howto/index.md +++ b/docs/howto/index.md @@ -1,4 +1,4 @@ -(howto-guides)= +(how-to-guides)= # How-to guides ```{toctree} @@ -22,7 +22,6 @@ Get started with charm testing Write unit tests for a charm Write scenario tests for a charm Write integration tests for a charm -Instrument your charm with tracing telemetry Turn a hooks-based charm into an ops charm ``` diff --git a/docs/howto/instrument-your-charm-with-tracing-telemetry.md b/docs/howto/instrument-your-charm-with-tracing-telemetry.md deleted file mode 100644 index 04b794c51..000000000 --- a/docs/howto/instrument-your-charm-with-tracing-telemetry.md +++ /dev/null @@ -1,146 +0,0 @@ -(how-to-instrument-your-charm-with-tracing-telemetry)= -# How to instrument your charm with tracing telemetry - -```{note} - -This howto has been incorporated into [the Tempo charm docs](https://charmhub.io/topics/charmed-tempo-ha/instrument%20your%20charm). - -``` - - -In order to instrument a charm with tracing telemetry, you will need to: - -1. Set up a model with `cos-lite`: - - -```bash -juju add-model clite -juju deploy cos-lite --trust -``` - -> See more: [Charmhub | `cos-lite`](https://charmhub.io/cos-lite) - -2. Deploy `tempo`: - -```bash -juju deploy tempo-k8s -``` - -> See more: [Charmhub | `tempo-k8s`](https://charmhub.io/tempo-k8s) - -3. Integrate tempo with `cos-lite`: - -```bash -jhack imatrix fill -``` - -> See more: {ref}``jhack` `, [GitHub | `jhack` > `imatrix` > `fill`](https://github.com/PietroPasotti/jhack#fill) - -Alternatively, you can integrate manually by: -``` -juju integrate tempo:logging loki:logging -juju integrate tempo:ingress traefik:ingress -juju integrate grafana:grafana-source tempo:grafana-source -juju integrate prometheus:metrics-endpoint, tempo:metrics-endpoint -juju integrate grafana:grafana-dashboard, tempo:grafana-dashboard -juju integrate traefik:tracing, tempo:tracing -juju integrate prometheus:tracing, tempo:tracing -``` - -```{note} - At some point there will be an overlay bundle to deploy `cos-lite` + `tempo` and integrate them; see [this PR](https://github.com/canonical/cos-lite-bundle/pull/79) to follow the progress on the overlay. -``` - -At this point you should have a working `cos-lite` plus `tempo` deployment; something like this: -![image|690x333](upload://7ohHj2JBT9Zaezsgx6p3a56n5cO.png) - -4. Fetch the `charm_tracing` and the `tracing` libs: -```bash -charmcraft fetch-lib charms.tempo-k8s.v1.charm_tracing -charmcraft fetch-lib charms.tempo-k8s.v2.tracing -``` - -> See more: {ref}``charmcraft fetch-lib` `, [Charmhub | `tempo-k8s` > Libraries > `charm-tracing`](https://charmhub.io/tempo-k8s/libraries/charm_tracing), [Charmhub | `tempo-k8s` > Libraries > `tracing`](https://charmhub.io/tempo-k8s/libraries/tracing) - -5. Add opentelemetry exporter dependency used by `charm_tracing` to your `requirements.txt`: - -``` -opentelemetry-exporter-otlp-proto-http>=1.21.0 -``` - -6. Add an integration with `tempo`: - -```yaml -# in charmcraft.yaml -provides: - tracing: - interface: tracing - limit: 1 -``` - -> See more: [File `charmcraft.yaml` > `provides`]() - -7. Instrument your charm code: - -```python -# in /your/charm/project/src/charm.py -from charms.tempo_k8s.v1.charm_tracing import trace_charm -from charms.tempo_k8s.v2.tracing import TracingEndpointRequirer - -@trace_charm(tracing_endpoint="tracing_endpoint") -class MyCharm(CharmBase): - def __init__(self, *args): - super().__init__(*args) - # Add a provider wrapper for the tracing endpoint. - self.tracing = TracingEndpointRequirer(self, protocols=["otlp_http"]) - - @property - def tracing_endpoint(self) -> Optional[str]: - """Tempo endpoint for charm tracing.""" - if self.tracing.is_ready(): - return self.tracing.get_endpoint("otlp_http") - return None -``` -```{note} - -By default, the traces generated by this charm will be tagged with service name equal to the class name of the charm, so `"MyCharm"` in this case. You override this default by passing -`service_name="foo"` to `trace_charm`. - -``` - - -8. Pack, deploy, and integrate your charm with `cos-lite`: - -```bash -charmcraft pack -juju deploy ./my-charm-operator_ubuntu-20.04-amd64.charm mycharm \ - $(yq eval '.resources | to_entries | map(select(.value.upstream-source != null) | "--resource " + .key + "=" + .value.upstream-source) | join(" ")' charmcraft.yaml) -juju integrate mycharm:tracing tempo-k8s:tracing -``` - -```{note} - -`cos-lite` is a kubernetes bundle. `tempo` is a Kubernetes charm. - -``` - -> See more: [`charmcraft pack` `, [Juju | `juju deploy`](https://juju.is/docs/juju/juju-deploy), [Juju | `juju integrate`](https://juju.is/docs/juju/juju-integrate) - -9. View the traces for the charm. - -Open the Grafana dashboard in a browser ([see here](https://github.com/canonical/grafana-k8s-operator) for more detailed instructions). - -Next, navigate to the traces for your charm: -- go to `Explore` and select the Tempo datasource. -- pick the `service_name` you gave to MyCharm above (the default is the application name) to see the traces for that charm -![image|690x505](upload://rAZOurgeYtFfymdB1V0qzKg7HcW.png) -- click on a trace ID to visualize it. For example, this is the trace for an `update-status` event on the Tempo charm itself: -![image|690x492](upload://6lufy3oZUYDkwxrnufQJKf5tpGN.png) - - -## Mapping events to traces with jhack tail -`jhack tail` supports a `-t` option to show the trace IDs associated with a charm execution: - -![image|607x326](upload://1UUxRnTkBR3hkCPfWd0sd4AXqfJ.png) - -This means that you can tail a charm, grab the trace id from tail, put it in the grafana dashboard query and get to the trace in no time. diff --git a/docs/howto/manage-actions.md b/docs/howto/manage-actions.md index 46b00d726..c9e8ed5f2 100644 --- a/docs/howto/manage-actions.md +++ b/docs/howto/manage-actions.md @@ -1,8 +1,9 @@ (manage-actions)= # How to manage actions + ## Implement the feature diff --git a/docs/howto/manage-configurations.md b/docs/howto/manage-configurations.md index aa788d44e..d80854f85 100644 --- a/docs/howto/manage-configurations.md +++ b/docs/howto/manage-configurations.md @@ -1,7 +1,9 @@ (manage-configurations)= # Manage configurations + ## Implement the feature diff --git a/docs/howto/manage-interfaces.md b/docs/howto/manage-interfaces.md index 588ddadae..cec28e89d 100644 --- a/docs/howto/manage-interfaces.md +++ b/docs/howto/manage-interfaces.md @@ -332,7 +332,7 @@ You should see: In particular, pay attention to the `provider` field. If it says `` then there is something wrong with your setup, and the collector isn't able to find your test or identify it as a valid test. -Similarly, you can add tests for requirer in `./interfaces/my_fancy_database/v0/interface_tests/test_requirer.py`. Don't forget to edit the `interface.yaml` file in the "requirers" section to add the name of the charm and the URL. See the "Edit `interface.yaml`" section in the previous how-to guide [How to register an interface]() for more detail on editing `interface.yaml`. [Here](https://github.com/IronCore864/charm-relation-interfaces/tree/my-fancy-database/interfaces/my_fancy_database/v0) is an example of tests for requirers added. +Similarly, you can add tests for requirer in `./interfaces/my_fancy_database/v0/interface_tests/test_requirer.py`. Don't forget to edit the `interface.yaml` file in the "requirers" section to add the name of the charm and the URL. See the "Edit `interface.yaml`" section in the previous how-to guide "How to register an interface" for more detail on editing `interface.yaml`. [Here](https://github.com/IronCore864/charm-relation-interfaces/tree/my-fancy-database/interfaces/my_fancy_database/v0) is an example of tests for requirers added. ### Merge in charm-relation-interfaces @@ -451,7 +451,7 @@ INFO:root:Running tests for role: provider } ``` -For reference, [here](https://github.com/IronCore864/my-fancy-database-operator) is an example of a bare minimum `my-fancy-database-operator` charm to make the test pass. In the charm, application relation data and unit relation data are set according to our definition (see the beginning part of the previous how-to guide [How to register an interface](). +For reference, [here](https://github.com/IronCore864/my-fancy-database-operator) is an example of a bare minimum `my-fancy-database-operator` charm to make the test pass. In the charm, application relation data and unit relation data are set according to our definition (see the beginning part of the previous how-to guide "How to register an interface". ### Troubleshooting and debugging the tests diff --git a/docs/howto/manage-leadership-changes.md b/docs/howto/manage-leadership-changes.md index 634487675..1546f056b 100644 --- a/docs/howto/manage-leadership-changes.md +++ b/docs/howto/manage-leadership-changes.md @@ -1,8 +1,9 @@ (manage-leadership-changes)= # Manage leadership changes + ## Implement response to leadership changes diff --git a/docs/howto/manage-libraries.md b/docs/howto/manage-libraries.md index b7ad88929..11d04c0f2 100644 --- a/docs/howto/manage-libraries.md +++ b/docs/howto/manage-libraries.md @@ -1,8 +1,9 @@ (manage-libraries)= # Manage libraries -> See first: [`juju` | Library](), [`charmcraft` | Manage libraries]() - + ## Write a library When you're writing libraries, instead of callbacks, you can use custom events; that'll result in a more `ops`-native-feeling API. A custom event is, from a technical standpoint, an EventBase subclass that can be emitted at any point throughout the charm's lifecycle. These events are therefore totally unknown to Juju. They are essentially charm-internal, and can be useful to abstract certain conditional workflows and wrap the toplevel Juju event so it can be observed independently. @@ -209,7 +210,9 @@ def test_my_object_data(context, endpoint, n_relations): Fetch the library. + In your `src/charm.py`, observe the custom events it puts at your disposal. For example, a database library may have provided a `database_relation_ready` event -- a high-level wrapper around the relevant `juju` relation events -- so you use it to manage the database integration in your charm as below: diff --git a/docs/howto/manage-logs.md b/docs/howto/manage-logs.md index 2073b705e..c2e9fe6e0 100644 --- a/docs/howto/manage-logs.md +++ b/docs/howto/manage-logs.md @@ -1,7 +1,9 @@ (how-to-log-a-message-in-a-charm)= # How to log a message in a charm + ## Implement the feature diff --git a/docs/howto/manage-resources.md b/docs/howto/manage-resources.md index d23143f40..d32cef5fa 100644 --- a/docs/howto/manage-resources.md +++ b/docs/howto/manage-resources.md @@ -1,7 +1,9 @@ (manage-resources)= # How to manage resources + ## Implement the feature diff --git a/docs/howto/manage-secrets.md b/docs/howto/manage-secrets.md index 3bfea3532..4c304f3a5 100644 --- a/docs/howto/manage-secrets.md +++ b/docs/howto/manage-secrets.md @@ -1,7 +1,9 @@ (manage-secrets)= # How to manage secrets + > Added in `ops 2.0.0`, `juju 3.0.2` diff --git a/docs/howto/manage-storage.md b/docs/howto/manage-storage.md index 1d39ac19e..6cb867ebc 100644 --- a/docs/howto/manage-storage.md +++ b/docs/howto/manage-storage.md @@ -1,7 +1,9 @@ (manage-storage)= # How to manage storage + ## Implement the feature diff --git a/docs/howto/run-workloads-with-a-charm-kubernetes.md b/docs/howto/run-workloads-with-a-charm-kubernetes.md index e89833c41..b06e80feb 100644 --- a/docs/howto/run-workloads-with-a-charm-kubernetes.md +++ b/docs/howto/run-workloads-with-a-charm-kubernetes.md @@ -5,7 +5,7 @@ The recommended way to create charms for Kubernetes is using the sidecar pattern Pebble is a lightweight, API-driven process supervisor designed for use with charms. If you specify the `containers` field in a charm's `charmcraft.yaml`, Juju will deploy the charm code in a sidecar container, with Pebble running as the workload container's `ENTRYPOINT`. -When the workload container starts up, Juju fires a [`PebbleReadyEvent`](https://ops.readthedocs.io/en/latest/#ops.PebbleReadyEvent), which can be handled using [`Framework.observe`](https://ops.readthedocs.io/en/latest/#ops.Framework.observe) as shown in [Framework Constructs under "Containers"](). This gives the charm author access to `event.workload`, a [`Container`](https://ops.readthedocs.io/en/latest/#ops.Container) instance. +When the workload container starts up, Juju fires a [`PebbleReadyEvent`](https://ops.readthedocs.io/en/latest/#ops.PebbleReadyEvent), which can be handled using [`Framework.observe`](https://ops.readthedocs.io/en/latest/#ops.Framework.observe). This gives the charm author access to `event.workload`, a [`Container`](https://ops.readthedocs.io/en/latest/#ops.Container) instance. The `Container` class has methods to modify the Pebble configuration "plan", start and stop services, read and write files, and run commands. These methods use the Pebble API, which communicates from the charm container to the workload container using HTTP over a Unix domain socket. diff --git a/docs/howto/turn-a-hooks-based-charm-into-an-ops-charm.md b/docs/howto/turn-a-hooks-based-charm-into-an-ops-charm.md index 82ea42969..aabacff51 100644 --- a/docs/howto/turn-a-hooks-based-charm-into-an-ops-charm.md +++ b/docs/howto/turn-a-hooks-based-charm-into-an-ops-charm.md @@ -1,7 +1,9 @@ (turn-a-hooks-based-charm-into-an-ops-charm)= # How to turn a hooks-based charm into an ops charm + Suppose you have a hooks-based charm and you decide to rewrite it using the Ops framework in Python. @@ -24,7 +26,7 @@ We start by looking at the charm we intend to translate; as an example, we will From the charm root directory we see: -```bash +```text $ tree . . ├── charmcraft.yaml diff --git a/docs/howto/write-integration-tests-for-a-charm.md b/docs/howto/write-integration-tests-for-a-charm.md index 1eff1607a..a0801d2cf 100644 --- a/docs/howto/write-integration-tests-for-a-charm.md +++ b/docs/howto/write-integration-tests-for-a-charm.md @@ -19,7 +19,9 @@ The instructions all use the Juju `python-libjuju` client, either through the `p In order to run integrations tests you will need to have your environment set up with `tox` installed. + ## Prepare the `tox.ini` configuration file @@ -42,7 +44,7 @@ commands = {[vars]tests_path}/integration ``` -

Create a test file

+## Create a test file By convention, integration tests are kept in the charm’s source tree, in a directory called `tests/integration`. @@ -55,7 +57,7 @@ from pytest_operator.plugin import OpsTest The `ops_test` fixture is your entry point to the `pytest-operator` library, and the preferred way of interacting with Juju in integration tests. This fixture will create a model for each test file -- if you write two tests that should not share a model, make sure to place them in different files. -

Build your tests

+## Build your tests ```{note} @@ -63,7 +65,7 @@ Use `pytest` custom markers to toggle which types of tests are being run so you ``` -

Test build and deploy

+### Test build and deploy To build and deploy the current charm, in your integration test file, add the function below: @@ -79,7 +81,7 @@ async def test_build_and_deploy(ops_test: OpsTest): Tests run sequentially in the order they are written in the file. It can be useful to put tests that build and deploy applications in the top of the file as the applications can be used by other tests. For that reason, adding extra checks or `asserts` in this test is not recommended. -The decorator `@pytest.mark.abort_on_fail` abort all next tests if something goes wrong. With the decorator `@pytest.mark.skip_if_deployed` you can skip that test if a `--model` is passed as a command line parameter (see [Run your tests](#run-tests) for more information). +The decorator `@pytest.mark.abort_on_fail` abort all next tests if something goes wrong. With the decorator `@pytest.mark.skip_if_deployed` you can skip that test if a `--model` is passed as a command line parameter (see {ref}`run-your-tests` for more information). `ops_test.build_charm` builds the charm with charmcraft. `ops_test.model` is an instance of `python-libjuju` 's [Model](https://pythonlibjuju.readthedocs.io/en/latest/api/juju.model.html#juju.model.Model) class that reference the active model tracked by `pytest-operator` for the current module. @@ -96,9 +98,11 @@ As an alternative to `wait_for_idle`, you can explicitly block until the applica > - [`pytest-operator` | `ops_test.build_charm`](https://github.com/charmed-kubernetes/pytest-operator/blob/ab50fc20320d3ea3d8a37495f92a004531a4023f/pytest_operator/plugin.py#L1020) > - [`python-libjuju` | `model.deploy `](https://github.com/juju/python-libjuju/blob/2581b0ced1df6201c6b7fd8cc0b20dcfa9d97c51/juju/model.py#L1658) -

Deploy your charm with resources

+### Deploy your charm with resources + A charm can require `file` or `oci-image` `resources` to work, that can be provided to `ops_test.model.deploy`. In Charmhub, resources have revision numbers. For file resources already stored in Charmhub, you can use `ops_test.download_resources`: @@ -130,7 +134,7 @@ For `oci-images` you can reference an image registry. > - [`python-libjuju` | `model.deploy`](https://github.com/juju/python-libjuju/blob/2581b0ced1df6201c6b7fd8cc0b20dcfa9d97c51/juju/model.py#L1658) -

Test a relation

+### Test a relation To test an integration between two applications, you can just integrate them through the model. Both applications have to be deployed beforehand. @@ -150,9 +154,12 @@ async def test_my_integration(ops_test: OpsTest): > See more: [`python-libjuju` | `model.integrate`](https://github.com/juju/python-libjuju/blob/2581b0ced1df6201c6b7fd8cc0b20dcfa9d97c51/juju/model.py#L1476) -

Test a configuration

+### Test a configuration + + You can set a configuration option in your application and check its results. @@ -171,13 +178,15 @@ async def test_config_changed(ops_test: OpsTest): -

Test an action

+### Test an action + You can execute an action on a unit and get its results. -``` +```text async def test_run_action(ops_test: OpsTest): action_register_user = await ops_test.model.applications["myapp"].units[0].run_action("register-user", username="ubuntu") await action_register_user.wait() @@ -188,13 +197,13 @@ async def test_run_action(ops_test: OpsTest): > See also: [python-libjuju | unit.run_action](https://github.com/juju/python-libjuju/blob/2581b0ced1df6201c6b7fd8cc0b20dcfa9d97c51/juju/unit.py#L274) -

Interact with the workload

+### Interact with the workload To interact with the workload, you need to have access to it. This is dependent on many aspects of your application, environment and network topology. You can get information from your application or unit addresses using `await ops_test.model.get_status`. That way, if your application exposes a public address you can reference it. You can also try to connect to a unit address or public address. -``` +```text async def test_workload_connectivity(ops_test: OpsTest): status = await ops_test.model.get_status() address = status.applications['my_app'].public_address @@ -210,15 +219,17 @@ How you can connect to a private or public address is dependent on your configur > Example implementations: [mongodb-k8s-operator](https://github.com/canonical/mongodb-k8s-operator/blob/8b9ebbee3f225ca98175c25781f1936dc4a62a7d/tests/integration/metrics_tests/test_metrics.py#L33), [tempo-k8s-operator](https://github.com/canonical/tempo-k8s-operator/blob/78a1143d99af99a1a56fe9ff82b1a3563e4fd2f7/tests/integration/test_integration.py#L69), [synapse](https://github.com/canonical/synapse-operator/blob/eb44f4959a00040f08b98470f8b17cae4cc616da/tests/integration/conftest.py#L170) + -

Run a subprocess command within Juju context

+### Run a subprocess command within Juju context You can run a command within the Juju context with: -``` +```text ... command = ["microk8s", "version"] returncode, stdout, stderr = await ops_test.run(*command, check=True) @@ -242,7 +253,7 @@ so you don't need to include the `-m` parameter. > - [`pytest-operator` | `run`](https://github.com/charmed-kubernetes/pytest-operator/blob/ab50fc20320d3ea3d8a37495f92a004531a4023f/pytest_operator/plugin.py#L576) > - [`pytest-operator` | `juju`](https://github.com/charmed-kubernetes/pytest-operator/blob/ab50fc20320d3ea3d8a37495f92a004531a4023f/pytest_operator/plugin.py#L624) -

Use several models

+### Use several models You can use `pytest-operator` with several models, in the same cloud or in different clouds. This way you can, for example, integrate machine charms @@ -274,25 +285,26 @@ Using the new alias, you can switch context to the new created model, similar to > Example implementations: [`charm-kubernetes-autoscaler`](https://github.com/charmed-kubernetes/charm-kubernetes-autoscaler/blob/8f4ddf5d66802ade73ed3aab2bb8d09fd9e4d63a/tests/integration/test_kubernetes_autoscaler.py#L31) + - -

Deploy a bundle

+### Deploy a bundle ```{note} -It is not recommended to use `ops_test.build_bundle` and `ops_test.deploy_bundle` until this [issue](https://github.com/charmed-kubernetes/pytest-operator/issues/98) is closed, as it uses `juju-bundle` which is outdated. You can deploy bundles using `ops_test.model.deploy` or [`ops_test.juju`](). +It is not recommended to use `ops_test.build_bundle` and `ops_test.deploy_bundle` until this [issue](https://github.com/charmed-kubernetes/pytest-operator/issues/98) is closed, as it uses `juju-bundle` which is outdated. You can deploy bundles using `ops_test.model.deploy` or `ops_test.juju`. ``` -

Render bundles and charms

+### Render bundles and charms `pytest-operator` has utilities to template your charms and bundles using Jinja2. @@ -324,7 +336,7 @@ async def test_build_and_deploy_bundle(ops_test: OpsTest): -

Speed up `update_status` with `fast_forward`

+### Speed up `update_status` with `fast_forward` If your charm code depends on the `update_status` event, you can speed up its firing rate with `fast_forward`. Inside the new async context you can put any code that will benefit from the new refresh rate so your test may execute faster. @@ -344,8 +356,8 @@ firing rate with `fast_forward`. Inside the new async context you can put any co > See more: > - [`pytest-operator` | `fast_forward`](https://github.com/charmed-kubernetes/pytest-operator/blob/ab50fc20320d3ea3d8a37495f92a004531a4023f/pytest_operator/plugin.py#L1400) - -

Run your tests

+(run-your-tests)= +## Run your tests By default you can run all your tests with: @@ -370,13 +382,15 @@ There are different ways of specifying a subset of tests to run using `pytest`. tox -e integration -- tests/integration/test_charm.py -k "not test_one" ``` + > See more: > - [`pytest-operator` | `skip_if_deployed`](https://github.com/charmed-kubernetes/pytest-operator/blob/ab50fc20320d3ea3d8a37495f92a004531a4023f/pytest_operator/plugin.py#L139) > - [`pytest | How to invoke pytest`](https://docs.pytest.org/en/7.1.x/how-to/usage.html) -

Generate crash dumps

+## Generate crash dumps To generate crash dumps, you need the `juju-crashdump` tool . @@ -394,6 +408,3 @@ tox -e integration -- --crash-dump=always --crash-dump-output=/tmp > See more: > - [`juju-crashdump`](https://github.com/juju/juju-crashdump) > - [`pytest-operator` | `--crash-dump`](https://github.com/charmed-kubernetes/pytest-operator/blob/ab50fc20320d3ea3d8a37495f92a004531a4023f/pytest_operator/plugin.py#L97) - - -> Contributors: @natalia-nowakowska , @javierdelapuente diff --git a/docs/index.md b/docs/index.md index 03776bf33..36acd1cb7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# Ops (`ops`) +# Ops documentation ```{toctree} :maxdepth: 2 @@ -12,19 +12,20 @@ explanation/index -The Ops (`ops`) library is a Python framework for writing and testing Juju charms. +The Ops library (`ops`) is a Python framework for writing and testing Juju charms. -The library ([available on PyPI](https://pypi.org/project/ops/)) provides: +> [See it on PyPI](https://pypi.org/project/ops/) -- {ref}`ops_main_entry_point`, used to initialise and run your charm; -- {ref}`ops`, the API to respond to Juju events and manage the application; -- {ref}`ops_pebble`, the Pebble client, a low-level API for Kubernetes containers; -- {ref}`ops_testing`, the recommended API for unit testing charms; and -- {ref}`ops_testing_harness`, the legacy (deprecated) API for unit testing charms. +The library provides: + +- {ref}`ops_main_entry_point`, used to initialise and run your charm +- {ref}`ops`, the API to respond to Juju events and manage the application +- {ref}`ops_pebble`, the Pebble client, a low-level API for Kubernetes containers +- {ref}`ops_testing`, the recommended API for unit testing charms +- {ref}`ops_testing_harness`, the deprecated API for unit testing charms You can structure your charm however you like, but with the `ops` library, you get a framework that promotes consistency and readability by following best practices. It also helps you organise your code better by separating different aspects of the charm, such as managing the application's state, handling integrations with other services, and making the charm easier to test. -If you're serious about charming, you *must* use `ops`! --------- @@ -83,7 +84,7 @@ As a community we adhere to the Ubuntu code of conduct. Discourse is the go-to forum for all questions Ops. * **[Join our online chat](https://matrix.to/#/#charmhub-ops:ubuntu.com)**: -Meet us in the `#ops` channel on Matrix. +Meet us in the #charmhub-charmdev channel on Matrix. * **[Report bugs](https://github.com/canonical/operator/)**: We want to know about the problems so we can fix them. diff --git a/docs/reference/api/index.md b/docs/reference/api/index.md deleted file mode 100644 index 2a5c77f38..000000000 --- a/docs/reference/api/index.md +++ /dev/null @@ -1,12 +0,0 @@ -(ops-api-index)= -# `ops` API reference - -```{toctree} -:maxdepth: 2 - -ops-main-entrypoint -ops -pebble -ops-testing -ops-testing-harness -``` diff --git a/docs/reference/index.md b/docs/reference/index.md index 8fbcc6269..196c5eb6c 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -2,13 +2,12 @@ # Reference ```{toctree} -:maxdepth: 2 - -charm-relation-interfaces -api/index -pytest-operator -testing -interface-tests +:maxdepth: 1 +ops-main-entrypoint +ops +pebble +ops-testing +ops-testing-harness ``` diff --git a/docs/reference/api/ops-main-entrypoint.rst b/docs/reference/ops-main-entrypoint.rst similarity index 100% rename from docs/reference/api/ops-main-entrypoint.rst rename to docs/reference/ops-main-entrypoint.rst diff --git a/docs/reference/api/ops-testing-harness.rst b/docs/reference/ops-testing-harness.rst similarity index 100% rename from docs/reference/api/ops-testing-harness.rst rename to docs/reference/ops-testing-harness.rst diff --git a/docs/reference/api/ops-testing.rst b/docs/reference/ops-testing.rst similarity index 100% rename from docs/reference/api/ops-testing.rst rename to docs/reference/ops-testing.rst diff --git a/docs/reference/api/ops.rst b/docs/reference/ops.rst similarity index 100% rename from docs/reference/api/ops.rst rename to docs/reference/ops.rst diff --git a/docs/reference/api/pebble.rst b/docs/reference/pebble.rst similarity index 100% rename from docs/reference/api/pebble.rst rename to docs/reference/pebble.rst diff --git a/docs/reference/pytest-operator.md b/docs/reference/pytest-operator.md deleted file mode 100644 index b86ec3767..000000000 --- a/docs/reference/pytest-operator.md +++ /dev/null @@ -1,19 +0,0 @@ -(pytest-operator)= -# `pytest-operator` - -`pytest-operator` is a Python library that provides Juju plugins for the generic Python library `pytest` to facilitate the {ref}`integration testing ` of charms. - -> See more: [`pytest-operator`](https://github.com/charmed-kubernetes/pytest-operator) - -It builds a fixture called `ops_test` that helps you interact with Juju through constructs that wrap around [`python-libjuju` ](https://pypi.org/project/juju/). - -> See more: -> - [`pytest-operator` > `ops_test`](https://github.com/charmed-kubernetes/pytest-operator/blob/main/docs/reference.md#ops_test) -> - [`pytest` > Fixtures](https://docs.pytest.org/en/6.2.x/fixture.html) - -It also provides convenient markers and command line parameters (e.g., the `@pytest.mark.skip_if_deployed` marker in combination with the `--no-deploy` configuration helps you skip, e.g., a deployment test in the case where you already have a deployment). - - -> See more: -> - [`pytest-operator` > Markers](https://github.com/charmed-kubernetes/pytest-operator/blob/main/docs/reference.md#markers) -> - [`pytest-operator` > Command line parameters](https://github.com/charmed-kubernetes/pytest-operator/blob/main/docs/reference.md#command-line-parameters) diff --git a/docs/resources/create_a_minimal_kubernetes_charm.jpg b/docs/resources/create_a_minimal_kubernetes_charm.png similarity index 100% rename from docs/resources/create_a_minimal_kubernetes_charm.jpg rename to docs/resources/create_a_minimal_kubernetes_charm.png diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/#expose-the-version-of-the-application-behind-your-charm.md# b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/#expose-the-version-of-the-application-behind-your-charm.md# new file mode 100644 index 000000000..2cc19f05e --- /dev/null +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/#expose-the-version-of-the-application-behind-your-charm.md# @@ -0,0 +1,124 @@ +(expose-the-version-of-the-application-behind-your-charm)= +# Expose the version of the application behind your charm + +> {ref}`From Zero to Hero: Write your first Kubernetes charm ` > Expose the version of the application behind your charm +> +> **See previous: {ref}`Make your charm configurable `** + +````{important} + +This document is part of a series, and we recommend you follow it in sequence. However, you can also jump straight in by checking out the code from the previous branches: + +``` +git clone https://github.com/canonical/juju-sdk-tutorial-k8s.git +cd juju-sdk-tutorial-k8s +git checkout 02_make_your_charm_configurable +git checkout -b 03_set_workload_version +``` + +```` + +In this chapter of the tutorial you will learn how to expose the version of the application (workload) run by the charm -- something that a charm user might find it useful to know. + +**Contents:** + +1. [Define functions to collect the workload application version and set it in the charm](#heading--define-functions-to-collect-the-workload-application-version-and-set-it-in-the-charm) +1. [Declare Python dependencies](#heading--declare-python-dependencies) +1. [Validate your charm](#heading--validate-your-charm) +1. [Review the final code](#heading--review-the-final-code) + +

Define functions to collect the workload application version and set it in the charm

+ +As a first step we need to add two helper functions that will send an HTTP request to our application to get its version. If the container is available, we can send a request using the `requests` Python library and then add class methods to parse the JSON output to get a version string, as shown below: + +- Import the `requests` Python library: + +``` +import requests +``` + +- Add the following class methods: + + +```python +@property +def version(self) -> str: + """Reports the current workload (FastAPI app) version.""" + try: + if self.container.get_services(self.pebble_service_name): + return self._request_version() + # Catching Exception is not ideal, but we don't care much for the error here, and just + # default to setting a blank version since there isn't much the admin can do! + except Exception as e: + logger.warning("unable to get version from API: %s", str(e), exc_info=True) + return "" + +def _request_version(self) -> str: + """Helper for fetching the version from the running workload using the API.""" + resp = requests.get(f"http://localhost:{self.config['server-port']}/version", timeout=10) + return resp.json()["version"] +``` + +Next, we need to update the `_update_layer_and_restart` method to set our workload version. Insert the following lines before setting `ActiveStatus`: + +```python +# Add workload version in Juju status. +self.unit.set_workload_version(self.version) +``` + +

Declare Python dependencies

+ + +Since we've added a third party Python dependency into our project, we need to list it in `requirements.txt`. Edit the file to add the following line: + +``` +requests~=2.28 +``` + +Next time you run `charmcraft` it will fetch this new dependency into the charm package. + + +

Validate your charm

+ +We've exposed the workload version behind our charm. Let's test that it's working! + +First, repack and refresh your charm: + +``` +charmcraft pack +juju refresh \ + --path="./demo-api-charm_ubuntu-22.04-amd64.charm" \ + demo-api-charm --force-units --resource \ + demo-server-image=ghcr.io/canonical/api_demo_server:1.0.1 +``` + +Our charm should fetch the application version and forward it to `juju`. Run `juju status` to check: + +```text +juju status +``` + +Indeed, the version of our workload is now displayed -- see the App block, the Version column: + +```text +Model Controller Cloud/Region Version SLA Timestamp +charm-model tutorial-controller microk8s/localhost 3.0.0 unsupported 12:37:27+01:00 + +App Version Status Scale Charm Channel Rev Address Exposed Message +demo-api-charm 1.0.1 active 1 demo-api-charm 0 10.152.183.233 no + +Unit Workload Agent Address Ports Message +demo-api-charm/0* active idle 10.1.157.75 +``` + +

Review the final code

+ + +For the full code see: [03_set_workload_version](https://github.com/canonical/juju-sdk-tutorial-k8s/tree/03_set_workload_version) + +For a comparative view of the code before and after this doc see: [Comparison](https://github.com/canonical/juju-sdk-tutorial-k8s/compare/02_make_your_charm_configurable...03_set_workload_version) + + +> **See next: {ref}`Integrate your charm with PostgreSQL `** + +> Contributors: @beliaev-maksim \ No newline at end of file diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.md index b72f6898c..c595a6184 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.md @@ -36,7 +36,7 @@ Conceptually, a charm is code that instructs Juju to deploy and manage an applic All subsequent workload management happens in the same way -- the Juju controller sends events to the charm and the charm responds to these events by managing the workload application in various ways via Pebble. The picture below illustrates all of this for a simple case where there is just one workload container. -![Create a minimal Kubernetes charm](../../resources/create_a_minimal_kubernetes_charm.jpg) +![Create a minimal Kubernetes charm](../../resources/create_a_minimal_kubernetes_charm.png) As a charm developer, your first job is to use this knowledge to create the basic structure and content for your charm: @@ -159,7 +159,7 @@ framework.observe(self.on.demo_server_pebble_ready, self._on_demo_server_pebble_ ```{tip} -**Pro tip:** Use `__init__` to hold references (pointers) to other `Object`s or immutable state only. That is because a charm is reinitialised on every event. See [Talking to a workload: Control flow from A to Z](). +**Pro tip:** Use `__init__` to hold references (pointers) to other `Object`s or immutable state only. That is because a charm is reinitialised on every event. ``` @@ -338,7 +338,7 @@ This name might vary slightly, depending on your architecture. E.g., for an `arm Deploy the `.charm` file, as below. Juju will create a Kubernetes `StatefulSet` named after your application with one replica. -``` +```text juju deploy ./demo-api-charm_ubuntu-22.04-amd64.charm --resource \ demo-server-image=ghcr.io/canonical/api_demo_server:1.0.1 ``` @@ -353,7 +353,7 @@ juju deploy ./demo-api-charm_ubuntu-22.04-amd64.charm --resource \ Monitor your deployment: -``` +```text juju status --watch 1s ``` @@ -382,25 +382,27 @@ You should see a JSON string with the version of the application: {"version":"1.0.0"} ``` ----- + ```{dropdown} Expand if you wish to inspect your deployment further 1. Run: - ``` + +```text kubectl get namespaces ``` + You should see that Juju has created a namespace called `welcome-k8s`. 2. Try: -``` +```text kubectl -n welcome-k8s get pods ``` You should see that your application has been deployed in a pod that has 2 containers running in it, one for the charm and one for the application. The containers talk to each other via the Pebble API using the UNIX socket. -``` +```text NAME READY STATUS RESTARTS AGE modeloperator-5df6588d89-ghxtz 1/1 Running 3 (7d2h ago) 13d demo-api-charm-0 2/2 Running 0 7d2h @@ -408,17 +410,11 @@ demo-api-charm-0 2/2 Running 0 7d2h 3. Check also: -``` +```text kubectl -n welcome-k8s describe pod demo-api-charm-0 ``` In the output you should see the definition for both containers. You'll be able to verify that the default command and arguments for our application container (`demo-server`) have been displaced by the Pebble service. You should be able to verify the same for the charm container (`charm`). - -``` - ------------- - - **Congratulations, you've successfully created a minimal Kubernetes charm!** diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/index.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/index.md index 623f1d785..e44dfb485 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/index.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/index.md @@ -1,7 +1,7 @@ (from-zero-to-hero-write-your-first-kubernetes-charm)= # From zero to hero: Write your first Kubernetes charm -This tutorial will introduce you to the official way to write a Kubernetes charm --- that is, how to equip an application with all the operational logic that it needs so that you can manage it on any Kubernetes cloud with just a few commands, using [Juju](https://juju.is/docs/olm). +This tutorial will introduce you to the official way to write a Kubernetes charm --- that is, how to equip an application with all the operational logic that it needs so that you can manage it on any Kubernetes cloud with just a few commands, using Juju. ```{important} @@ -23,9 +23,9 @@ The application that we will charm in this tutorial is based on the Python Fast **What you'll need:** - A working station, e.g., a laptop with amd64 architecture. -- Familiarity with [Juju](https://juju.is/docs/juju) -- Familiarity with the Python programming language, Object-Oriented Programming, Event Handlers -- Understanding of Kubernetes fundamentals +- Familiarity with Juju. +- Familiarity with the Python programming language, Object-Oriented Programming, event handlers. +- Understanding of Kubernetes fundamentals. **What you'll do:** @@ -77,13 +77,8 @@ By the end of this tutorial you will have built a machine charm and evolved it i | If you are wondering... | visit... | |-------------------------|----------------------| -| "How do I...?" | {ref}`howto-guides` | +| "How do I...?" | {ref}`how-to-guides` | | "What is...?" | {ref}`reference` | | "Why...?", "So what?" | {ref}`explanation` | - - -
- -> **Contributors:** @beliaev-maksim diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/observe-your-charm-with-cos-lite.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/observe-your-charm-with-cos-lite.md index 0dd3c8f1f..90f8345ca 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/observe-your-charm-with-cos-lite.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/observe-your-charm-with-cos-lite.md @@ -449,7 +449,8 @@ In a while you should see the following data appearing on the dashboard: 3. Logs from your application that were collected by Loki and forwarded to Grafana. Here you can see some INFO level logs and ERROR logs with traceback from Python when you were calling the `/error` path. -![Observe your charm with COS Lite](../../resources/observe_your_charm_with_cos_lite.png) +![Observe your charm with COS Lite](../../resources/observe_your_charm_with_cos_lite.png) + ```{important} diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/preserve-your-charms-data.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/preserve-your-charms-data.md index 6d2f14d2b..49dc7a924 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/preserve-your-charms-data.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/preserve-your-charms-data.md @@ -28,15 +28,21 @@ There are a few strategies you can adopt here: First, you can use an Ops construct called `Stored State`. With this strategy you can store data on the local unit (at least, so long as your `main` function doesn't set `use_juju_for_storage` to `True`). However, if your Kubernetes pod dies, your unit also dies, and thus also the data. For this reason this strategy is generally not recommended. -> Read more: [StoredState](), {ref}`StoredState: Uses, Limitations ` +> Read more: [`ops.StoredState`](https://ops.readthedocs.io/en/latest/#ops.StoredState), {ref}`StoredState: Uses, Limitations ` Second, you can make use of the Juju notion of 'peer relations' and 'data bags' and set up a peer relation data bag. This will help you store the information in the Juju's database backend. -> Read more: [Peer integrations](https://juju.is/docs/juju/relation#heading--peer) + + Third, when you have confidential data, you can use Juju secrets (from Juju 3.1 onwards). + + In this chapter we will adopt the second strategy, that is, we will store charm data in a peer relation databag. (We will explore the third strategy in a different scenario in the next chapter.) We will illustrate this strategy with an artificial example where we save the counter of how many times the application pod has been restarted. @@ -49,7 +55,10 @@ peers: fastapi-peer: interface: fastapi_demo_peers ``` + + ## Set and get data from the peer relation databag diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/publish-your-charm-on-charmhub.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/publish-your-charm-on-charmhub.md index 74f76f35e..7d49aafe6 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/publish-your-charm-on-charmhub.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/publish-your-charm-on-charmhub.md @@ -82,7 +82,7 @@ echo "Random 8-digit hash: $random_hash" ``` ```{important} -Naming your charm is usually less random than that; see [Charm naming guidelines](). However, here we are in a tutorial setting, so you just need to make sure to pick a unique name, any name. +Naming your charm is usually less random than that. However, here we are in a tutorial setting, so you just need to make sure to pick a unique name, any name. ``` @@ -161,7 +161,7 @@ Release your charm as below. ```{important} **Do not worry:**
-While releasing a charm to Charmhub gives it a public URL, the charm will not appear in the Charmhub search results until it has passed formal review -- see [Requirements for public listing](). +While releasing a charm to Charmhub gives it a public URL, the charm will not appear in the Charmhub search results until it has passed formal review. ``` diff --git a/docs/tutorial/write-your-first-machine-charm.md b/docs/tutorial/write-your-first-machine-charm.md index 1bd48dda5..f3c8588df 100644 --- a/docs/tutorial/write-your-first-machine-charm.md +++ b/docs/tutorial/write-your-first-machine-charm.md @@ -1,18 +1,18 @@ (write-your-first-machine-charm)= # Write your first machine charm -In this tutorial you will write a [machine charm](https://juju.is/docs/juju/charmed-operator) for [`juju`](https://juju.is/docs/juju) using (`charmcraft` and) `ops`. +In this tutorial you will write a [machine charm](https://juju.is/docs/juju/charmed-operator) for Juju using (Charmcraft and) Ops. **What you'll need:** - A workstation, e.g., a laptop, with amd64 architecture and which has sufficient resources to launch a virtual machine with 4 CPUs, 8 GB RAM, and 50 GB disk space - Familiarity with Linux -- Familiarity with [Juju](https://juju.is/docs/juju) +- Familiarity with Juju. - Familiarity with object-oriented programming in Python **What you'll do:** -Study your application. Use `charmcraft` and `ops` to build a basic charm and test-deploy it with `juju` and a localhost LXD-based cloud. Repeat the steps to evolve the charm so it can become increasingly more sophisticated. +Study your application. Use Charmcraft and Ops to build a basic charm and test-deploy it with Juju and a localhost LXD-based cloud. Repeat the steps to evolve the charm so it can become increasingly more sophisticated. @@ -35,7 +35,8 @@ The application has other features that we can exploit, but for now this is enou ## Set up your development environment -> See [`juju` | Set up your development environment automatically](https://juju.is/docs/juju/set-up--tear-down-your-test-environment#set-up-automatically) for instructions on how to set up your development environment so that it's ready for you to test-deploy your charm. At the charm directory step, call it `microsample-vm`. At the cloud step, choose LXD. + +> See [Juju | Set up your development environment automatically](https://juju.is/docs/juju/set-up--tear-down-your-test-environment#set-up-automatically) for instructions on how to set up your development environment so that it's ready for you to test-deploy your charm. At the charm directory step, call it `microsample-vm`. At the cloud step, choose LXD. ```{important} @@ -94,7 +95,9 @@ test_charm.py ``` + In your local editor, open the `charmcraft.yaml` file and customise its contents as below (you only have to edit the `title`, `summary`, and `description`): @@ -132,8 +135,9 @@ bases: channel: "22.04" ``` - + Now open the `src/charm.py` file and update it as below (you'll have to add an import statement for `os` and an observer and handler for the `install` event -- in the definition of which you'll be using `os` and `ops`). @@ -167,7 +171,9 @@ if __name__ == "__main__": # pragma: nocover ops.main(MicrosampleVmCharm) # type: ignore ``` + Next, in your Multipass VM shell, inside your project directory, run `charmcraft pack` to pack the charm. It may take a few minutes the first time around but, when it's done, your charm project should contain a `.charm` file. Sample session: @@ -186,7 +192,9 @@ LICENSE microsample-vm_ubuntu-22.04-amd64.charm src README.md pyproject.toml tests ``` + Now, open a new shell into your Multipass VM and use it to configure the Juju log verbosity levels and to start a live debug session: @@ -281,7 +289,9 @@ config: type: string ``` + Then, in the `src/charm.py` file, update the `_on_install` function to make use of the new configuration option, as below: @@ -401,7 +411,9 @@ requests==2.28.1 requests-unixsocket==0.3.0 ``` -> See more: [`charmcraft` | File `requirements.txt` `](), [PyPI > Library `requests`](https://pypi.org/project/requests/), [PyPI > Library `requests-unixsocket`](https://pypi.org/project/requests-unixsocket/) + Then, in your `src/charm.py` file, import the `requests_unixsocket` package, update the `_on_config_changed` function to set the workload version to the output of a function `_getWorkloadVersion`, and define the function to retrieve the Microsample workload version from the `snapd` API via a Unix socket, as below: @@ -510,9 +522,12 @@ Machine State Address Inst id Base AZ Message Congratulations, your charm user can view the version of the workload deployed from your charm! + ## Tear things down -> See [`juju` | Tear down your development environment automatically](https://juju.is/docs/juju/set-up--tear-down-your-test-environment#tear-down-automatically) +> See [Juju | Tear down your development environment automatically](https://juju.is/docs/juju/set-up--tear-down-your-test-environment#tear-down-automatically) + + (tutorial-machines-next-steps)= ## Next steps @@ -521,7 +536,7 @@ By the end of this tutorial you will have built a machine charm and evolved it i | If you are wondering... | visit... | |-------------------------|----------------------| -| "How do I...?" | {ref}`howto-guides` | +| "How do I...?" | {ref}`how-to-guides` | | "What is...?" | {ref}`reference` | | "Why...?", "So what?" | {ref}`explanation` |