From a3757494fa5e7f60f868cfb0ab7a1bed75fe33f0 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Thu, 2 Jan 2025 12:14:46 +0800 Subject: [PATCH 1/9] Updated links to ops.testing root --- docs/explanation/testing.md | 2 +- docs/howto/get-started-with-charm-testing.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/explanation/testing.md b/docs/explanation/testing.md index 65a55dfbe..bb35b532a 100644 --- a/docs/explanation/testing.md +++ b/docs/explanation/testing.md @@ -31,7 +31,7 @@ Unit tests are intended to be isolating and fast to complete. These are the test **Tools.** Unit testing a charm can be done using: - [`pytest`](https://pytest.org/) and/or [`unittest`](https://docs.python.org/3/library/unittest.html) and -- [state transition testing](https://ops.readthedocs.io/en/latest/reference/ops-testing.html), using the `ops` unit testing framework +- [state transition testing](ops_testing), using the `ops` unit testing framework **Examples.** diff --git a/docs/howto/get-started-with-charm-testing.md b/docs/howto/get-started-with-charm-testing.md index b52ec87f0..deea4b8d4 100644 --- a/docs/howto/get-started-with-charm-testing.md +++ b/docs/howto/get-started-with-charm-testing.md @@ -95,7 +95,7 @@ You will notice that the starting point is typically always an event. A charm do In the charming world, unit testing means state-transition testing. -> See more [`ops.testing`](https://ops.readthedocs.io/en/latest/reference/ops-testing.html) +> See more [`ops.testing`](ops_testing) `State` is the 'mocker' for most inputs and outputs you will need. Where a live charm would gather its input through context variables and calls to the Juju API (by running the hook tools), a charm under unit test will gather data using a mocked backend managed by the testing framework. Where a live charm would produce output by writing files to a filesystem, `Context` and `Container` expose a mock filesystem the charm will be able to interact with without knowing the difference. More specific outputs, however, will need to be mocked individually. From c34ae8efef17586aac6ccb887d78fe60f82dab4c Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Thu, 2 Jan 2025 12:34:29 +0800 Subject: [PATCH 2/9] Updated links to ops.testing items generated by autoclass --- docs/howto/manage-actions.md | 2 +- docs/howto/manage-relations.md | 2 +- docs/howto/manage-storage.md | 2 +- docs/howto/run-workloads-with-a-charm-kubernetes.md | 2 +- docs/howto/write-scenario-tests-for-a-charm.md | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/howto/manage-actions.md b/docs/howto/manage-actions.md index c4c03c05b..d8a7f84d4 100644 --- a/docs/howto/manage-actions.md +++ b/docs/howto/manage-actions.md @@ -172,7 +172,7 @@ def test_backup_action(): assert 'snapshot-size' in ctx.action_results ``` -> See more: [`Context.action_logs`](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.Context.action_logs), [`Context.action_results`](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.Context.action_results), [`ActionFailed`](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.ActionFailed) +> See more: [](ops.testing.Context.action_logs), [](ops.testing.Context.action_results), [](ops.testing.ActionFailed) ### Write integration tests diff --git a/docs/howto/manage-relations.md b/docs/howto/manage-relations.md index 211d4c8e4..d818b2269 100644 --- a/docs/howto/manage-relations.md +++ b/docs/howto/manage-relations.md @@ -273,7 +273,7 @@ state_out = ctx.run(ctx.on.relation_joined(relation, remote_unit_id=1), state=st assert 'smtp_credentials' in state_out.get_relation(relation.id).remote_units_data[1] ``` -> See more: [Scenario Relations](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.RelationBase) +> See more: [Scenario Relations](ops.testing.RelationBase) ### Write integration tests diff --git a/docs/howto/manage-storage.md b/docs/howto/manage-storage.md index 5cf2686f4..b2de25ad9 100644 --- a/docs/howto/manage-storage.md +++ b/docs/howto/manage-storage.md @@ -191,7 +191,7 @@ foo_1 = testing.Storage('foo') ctx.run(ctx.on.storage_attached(foo_1), testing.State(storages={foo_0, foo_1})) ``` -> See more: [`ops.testing.Storage`](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.Storage) +> See more: [](ops.testing.Storage) ### Write integration tests diff --git a/docs/howto/run-workloads-with-a-charm-kubernetes.md b/docs/howto/run-workloads-with-a-charm-kubernetes.md index d1140b4ed..1dcbbfc9c 100644 --- a/docs/howto/run-workloads-with-a-charm-kubernetes.md +++ b/docs/howto/run-workloads-with-a-charm-kubernetes.md @@ -873,7 +873,7 @@ A charm can also query for notices using the following two `Container` methods: ### Test notices -To test charms that use Pebble Notices, use the [`pebble_custom_notice`](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.CharmEvents.pebble_custom_notice) method to simulate recording a notice with the given details. For example, to simulate the "backup-done" notice handled above, the charm tests could do the following: +To test charms that use Pebble Notices, use the [`pebble_custom_notice`](ops.testing.CharmEvents.pebble_custom_notice) method to simulate recording a notice with the given details. For example, to simulate the "backup-done" notice handled above, the charm tests could do the following: ```python from ops import testing diff --git a/docs/howto/write-scenario-tests-for-a-charm.md b/docs/howto/write-scenario-tests-for-a-charm.md index 5a79dd3ca..801bb0bfd 100644 --- a/docs/howto/write-scenario-tests-for-a-charm.md +++ b/docs/howto/write-scenario-tests-for-a-charm.md @@ -60,8 +60,8 @@ def test_charm_runs(): ``` > See more: -> - [State](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.State) -> - [Context](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops.testing.Context) +> - [`State`](ops.testing.State) +> - [`Context`](ops.testing.Context) ```{note} From 8d62861e4817ba0eeae5b2802c386f3ec6b79b16 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Thu, 2 Jan 2025 14:01:28 +0800 Subject: [PATCH 3/9] Updated links to Harness (use full doc title so that "legacy" is mentioned) --- docs/howto/write-unit-tests-for-a-charm.md | 8 ++++---- .../write-unit-tests-for-your-charm.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/howto/write-unit-tests-for-a-charm.md b/docs/howto/write-unit-tests-for-a-charm.md index 042e87e31..9b2dca73f 100644 --- a/docs/howto/write-unit-tests-for-a-charm.md +++ b/docs/howto/write-unit-tests-for-a-charm.md @@ -42,7 +42,7 @@ def test_config_changed(harness: ops.testing.Harness[TestCharmCharm]): ``` -We use [`pytest` unit testing framework](https://docs.pytest.org) (Python’s standard [unit testing framework](https://docs.python.org/3/library/unittest.html) is a valid alternative), augmenting it with [`Harness`](https://ops.readthedocs.io/en/latest/#ops.testing.Harness), the Ops library's testing harness. [`Harness`](https://ops.readthedocs.io/en/latest/#ops.testing.Harness) provides some convenient mechanisms for mocking charm events and processes. +We use [`pytest` unit testing framework](https://docs.pytest.org) (Python’s standard [unit testing framework](https://docs.python.org/3/library/unittest.html) is a valid alternative), augmenting it with [](ops_testing_harness). `Harness` provides some convenient mechanisms for mocking charm events and processes. A common pattern is to specify some minimal `metadata.yaml` content for testing like this: @@ -98,7 +98,7 @@ harness.charm.unit.status = BlockedStatus("Testing") Any of your charm’s properties and methods (including event callbacks) can be accessed using `harness.charm`. You can check out the [harness API -docs](https://ops.readthedocs.io/en/latest/index.html#ops.testing.Harness) for more ways to use the +docs](ops_testing_harness) for more ways to use the harness to trigger other events and to test your charm (e.g. triggering leadership-related events, testing pebble events and sidecar container interactions, etc.). @@ -138,9 +138,9 @@ In `ops` 1.4, functionality was added to the Harness to more accurately track co Containers normally start in a disconnected state, and any interaction with the remote container (push, pull, add_layer, and so on) will raise an `ops.pebble.ConnectionError`. To mark a container as connected, -you can either call [`harness.set_can_connect(container, True)`](https://ops.readthedocs.io/en/latest/#ops.testing.Harness.set_can_connect), or you can call [`harness.container_pebble_ready(container)`](https://ops.readthedocs.io/en/latest/#ops.testing.Harness.container_pebble_ready) if you want to mark the container as connected *and* trigger its pebble-ready event. +you can either call [`harness.set_can_connect(container, True)`](ops.testing.Harness.set_can_connect), or you can call [`harness.container_pebble_ready(container)`](ops.testing.Harness.container_pebble_ready) if you want to mark the container as connected *and* trigger its pebble-ready event. -However, if you're using [`harness.begin_with_initial_hooks()`](https://ops.readthedocs.io/en/latest/#ops.testing.Harness.begin_with_initial_hooks) in your tests, that will automatically call `container_pebble_ready()` for all containers in the charm's metadata, so you don't have to do it manually. +However, if you're using [`harness.begin_with_initial_hooks()`](ops.testing.Harness.begin_with_initial_hooks) in your tests, that will automatically call `container_pebble_ready()` for all containers in the charm's metadata, so you don't have to do it manually. If you have a hook that pushes a file to the container, like this: diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-unit-tests-for-your-charm.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-unit-tests-for-your-charm.md index 2f9ff9cc4..372d1d4e6 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-unit-tests-for-your-charm.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-unit-tests-for-your-charm.md @@ -22,7 +22,7 @@ When you're writing a charm, you will want to ensure that it will behave reliabl For example, that the various components -- relation data, pebble services, or configuration files -- all behave as expected in response to an event. -You can ensure all this by writing a rich battery of units tests. In the context of a charm we recommended using [`pytest`](https://pytest.org/) (but [`unittest`](https://docs.python.org/3/library/unittest.html) can also be used) and especially the operator framework's built-in testing library -- [`ops.testing.Harness`](https://ops.readthedocs.io/en/latest/harness.html#module-ops.testing). We will be using the Python testing tool [`tox`](https://tox.wiki/en/4.14.2/index.html) to automate our testing and set up our testing environment. +You can ensure all this by writing a rich battery of units tests. In the context of a charm we recommended using [`pytest`](https://pytest.org/) (but [`unittest`](https://docs.python.org/3/library/unittest.html) can also be used) and especially the operator framework's built-in testing library -- [](ops_testing_harness). We will be using the Python testing tool [`tox`](https://tox.wiki/en/4.14.2/index.html) to automate our testing and set up our testing environment. In this chapter you will write a simple unit test to check that your workload container is initialised correctly. @@ -146,7 +146,7 @@ def test_pebble_layer( ``` -> Read more: [`ops.testing`](https://ops.readthedocs.io/en/latest/harness.html#module-ops.testing) +> Read more: [](ops_testing_harness) ## Run the test From 1d8374ed7b1bd04dc421be1f8bdbec21df82df51 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Thu, 2 Jan 2025 14:16:09 +0800 Subject: [PATCH 4/9] Updated broken absolute links --- docs/explanation/holistic-vs-delta-charms.md | 2 +- docs/howto/run-workloads-with-a-charm-kubernetes.md | 2 +- docs/howto/write-unit-tests-for-a-charm.md | 2 +- .../open-a-kubernetes-port-in-your-charm.md | 2 +- .../preserve-your-charms-data.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/explanation/holistic-vs-delta-charms.md b/docs/explanation/holistic-vs-delta-charms.md index e7444d646..fdebfdfe3 100644 --- a/docs/explanation/holistic-vs-delta-charms.md +++ b/docs/explanation/holistic-vs-delta-charms.md @@ -59,7 +59,7 @@ Many existing charms use holistic event handling. A few examples are: Only some events make sense to handle holistically. For example, `remove` is triggered when a unit is about to be terminated, so it doesn't make sense to handle it holistically. -Similarly, events like `secret-expired` and `secret-rotate` don't make sense to handle holistically, because the charm must do something specific in response to the event. For example, Juju will keep triggering `secret-expired` until the charm creates a new secret revision by calling [`event.secret.set_content()`](https://ops.readthedocs.io/en/latest/#ops.Secret.set_content). +Similarly, events like `secret-expired` and `secret-rotate` don't make sense to handle holistically, because the charm must do something specific in response to the event. For example, Juju will keep triggering `secret-expired` until the charm creates a new secret revision by calling [`event.secret.set_content()`](ops.Secret.set_content). This is very closely related to [which events can be `defer`red](https://juju.is/docs/sdk/how-and-when-to-defer-events). A good rule of thumb is this: if an event can be deferred, it may make sense to handle it holistically. diff --git a/docs/howto/run-workloads-with-a-charm-kubernetes.md b/docs/howto/run-workloads-with-a-charm-kubernetes.md index 1dcbbfc9c..fd16ba914 100644 --- a/docs/howto/run-workloads-with-a-charm-kubernetes.md +++ b/docs/howto/run-workloads-with-a-charm-kubernetes.md @@ -306,7 +306,7 @@ To stop a service, Pebble first sends `SIGTERM` to the service's process group t ### Fetch service status -You can use the [`get_service`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_service) and [`get_services`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_services) methods to fetch the current status of one service or multiple services, respectively. The returned [`ServiceInfo`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ServiceInfo) objects provide a `status` attribute with various states, or you can use the [`ServiceInfo.is_running`](https://ops.readthedocs.io/en/latest/#ops.pebble.ServiceInfo.is_running) method. +You can use the [`get_service`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_service) and [`get_services`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_services) methods to fetch the current status of one service or multiple services, respectively. The returned [`ServiceInfo`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ServiceInfo) objects provide a `status` attribute with various states, or you can use the [`ServiceInfo.is_running`](ops.pebble.ServiceInfo.is_running) method. Here is a modification to the start/stop example that checks whether the service is running before stopping it: diff --git a/docs/howto/write-unit-tests-for-a-charm.md b/docs/howto/write-unit-tests-for-a-charm.md index 9b2dca73f..55ac42be2 100644 --- a/docs/howto/write-unit-tests-for-a-charm.md +++ b/docs/howto/write-unit-tests-for-a-charm.md @@ -173,7 +173,7 @@ def test_something(harness): harness.update_config(key_values={'the-answer': 42}) ``` -Which suggests that your `_config_changed` hook should probably use [`Container.can_connect()`](https://ops.readthedocs.io/en/latest/#ops.Container.can_connect): +Which suggests that your `_config_changed` hook should probably use [`Container.can_connect()`](ops.Container.can_connect): ```python def _config_changed(event): diff --git a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/open-a-kubernetes-port-in-your-charm.md b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/open-a-kubernetes-port-in-your-charm.md index a0588e693..3a313f4d3 100644 --- a/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/open-a-kubernetes-port-in-your-charm.md +++ b/docs/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/open-a-kubernetes-port-in-your-charm.md @@ -49,7 +49,7 @@ def _handle_ports(self) -> None: self.unit.set_ports(port) ``` -> See more: [`ops.Unit.set_ports`](https://ops.readthedocs.io/en/latest/#ops.Unit.set_ports) +> See more: [](ops.Unit.set_ports) ## Test the new feature 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 49dc7a924..bc3e2bc5f 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,7 +28,7 @@ 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: [`ops.StoredState`](https://ops.readthedocs.io/en/latest/#ops.StoredState), {ref}`StoredState: Uses, Limitations ` +> Read more: [](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. From 3612f02d227f4687aaa2cfba293f63796d69c9c4 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Fri, 3 Jan 2025 15:11:30 +0800 Subject: [PATCH 5/9] Updated links to ops.pebble --- .../run-workloads-with-a-charm-kubernetes.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/howto/run-workloads-with-a-charm-kubernetes.md b/docs/howto/run-workloads-with-a-charm-kubernetes.md index fd16ba914..b2a2fb32c 100644 --- a/docs/howto/run-workloads-with-a-charm-kubernetes.md +++ b/docs/howto/run-workloads-with-a-charm-kubernetes.md @@ -14,7 +14,7 @@ The rest of this document provides details of how a charm interacts with the wor ```{note} -The [`Container.pebble`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.pebble) property returns the [`pebble.Client`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.Client) instance for the given container. +The [`Container.pebble`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.pebble) property returns the [`pebble.Client`](ops.pebble.Client) instance for the given container. ``` ## Set up the workload container @@ -204,7 +204,7 @@ See the [layer specification](https://canonical-pebble.readthedocs-hosted.com/en #### Add a configuration layer -To add a configuration layer, call [`Container.add_layer`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.add_layer) with a label for the layer, and the layer's contents as a YAML string, Python dict, or [`pebble.Layer`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.Layer) object. +To add a configuration layer, call [`Container.add_layer`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.add_layer) with a label for the layer, and the layer's contents as a YAML string, Python dict, or [`pebble.Layer`](#ops.pebble.Layer) object. You can see an example of `add_layer` under the ["Replan" heading](#replan). The `combine=True` argument tells Pebble to combine the named layer into an existing layer of that name (or add a layer if none by that name exists). Using `combine=True` is common when dynamically adding layers. @@ -214,7 +214,7 @@ If you're adding a single layer with `combine=False` (default option) on top of #### Fetch the effective plan -Charm authors can also introspect the current plan using [`Container.get_plan`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_plan). It returns a [`pebble.Plan`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.Plan) object whose `services` attribute maps service names to [`pebble.Service`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.Service) instances. +Charm authors can also introspect the current plan using [`Container.get_plan`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_plan). It returns a [`pebble.Plan`](ops.pebble.Plan) object whose `services` attribute maps service names to [`pebble.Service`](ops.pebble.Service) instances. It is not necessary to use `get_plan` to determine whether the plan has changed and start services accordingly. If you call [`replan`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.replan), then Pebble will take care of this for you. @@ -306,7 +306,7 @@ To stop a service, Pebble first sends `SIGTERM` to the service's process group t ### Fetch service status -You can use the [`get_service`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_service) and [`get_services`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_services) methods to fetch the current status of one service or multiple services, respectively. The returned [`ServiceInfo`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ServiceInfo) objects provide a `status` attribute with various states, or you can use the [`ServiceInfo.is_running`](ops.pebble.ServiceInfo.is_running) method. +You can use the [`get_service`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_service) and [`get_services`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_services) methods to fetch the current status of one service or multiple services, respectively. The returned [`ServiceInfo`](ops.pebble.ServiceInfo) objects provide a `status` attribute with various states, or you can use the [`ServiceInfo.is_running`](ops.pebble.ServiceInfo.is_running) method. Here is a modification to the start/stop example that checks whether the service is running before stopping it: @@ -476,7 +476,7 @@ class PostgresCharm(ops.CharmBase): ### Fetch check status -You can use the [`get_check`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_check) and [`get_checks`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_checks) methods to fetch the current status of one check or multiple checks, respectively. The returned [`CheckInfo`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.CheckInfo) objects provide various attributes, most importantly a `status` attribute which will be either `UP` or `DOWN`. +You can use the [`get_check`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_check) and [`get_checks`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.get_checks) methods to fetch the current status of one check or multiple checks, respectively. The returned [`CheckInfo`](ops.pebble.CheckInfo) objects provide various attributes, most importantly a `status` attribute which will be either `UP` or `DOWN`. Here is a code example that checks whether the `uptime` check is healthy, and writes an error log if not: @@ -601,7 +601,7 @@ A trailing "/*" on the source directory is the only supported globbing/matching. ### List files -To list the contents of a directory or return stat-like information about one or more files, use [`Container.list_files`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.list_files). It returns a list of [`pebble.FileInfo`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.FileInfo) objects for each entry (file or directory) in the given path, optionally filtered by a glob pattern. For example: +To list the contents of a directory or return stat-like information about one or more files, use [`Container.list_files`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.list_files). It returns a list of [`pebble.FileInfo`](ops.pebble.FileInfo) objects for each entry (file or directory) in the given path, optionally filtered by a glob pattern. For example: ```python infos = container.list_files('/etc', pattern='*.conf') @@ -652,7 +652,7 @@ container.isdir('/tmp/mydir') # True Pebble includes an API for executing arbitrary commands on the workload container: the [`Container.exec`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Container.exec) method. It supports sending stdin to the process and receiving stdout and stderr, as well as more advanced options. -To run simple commands and receive their output, call `Container.exec` to start the command, and then use the returned [`Process`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecProcess) object's [`wait_output`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecProcess.wait_output) method to wait for it to finish and collect its output. +To run simple commands and receive their output, call `Container.exec` to start the command, and then use the returned [`Process`](ops.pebble.ExecProcess) object's [`wait_output`](ops.pebble.ExecProcess.wait_output) method to wait for it to finish and collect its output. For example, to back up a PostgreSQL database, you might use `pg_dump`: @@ -667,9 +667,9 @@ if warnings: ### Handle errors -The `exec` method raises a [`pebble.APIError`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.APIError) if basic checks fail and the command can't be executed at all, for example, if the executable is not found. +The `exec` method raises a [`pebble.APIError`](ops.pebble.APIError) if basic checks fail and the command can't be executed at all, for example, if the executable is not found. -The [`ExecProcess.wait`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecProcess.wait) and [`ExecProcess.wait_output`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecProcess.wait_output) methods raise [`pebble.ChangeError`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ChangeError) if there was an error starting or running the process, and [`pebble.ExecError`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecError) if the process exits with a non-zero exit code. +The [`ExecProcess.wait`](ops.pebble.ExecProcess.wait) and [`ExecProcess.wait_output`](ops.pebble.ExecProcess.wait_output) methods raise [`pebble.ChangeError`](ops.pebble.ChangeError) if there was an error starting or running the process, and [`pebble.ExecError`](ops.pebble.ExecError) if the process exits with a non-zero exit code. In the case where the process exits via a signal (such as SIGTERM or SIGKILL), the exit code will be 128 plus the signal number. SIGTERM's signal number is 15, so a process terminated via SIGTERM would give exit code 143 (128+15). @@ -696,7 +696,7 @@ Exited with code 1. Stderr: ### Use command options -The `Container.exec` method has various options (see [full API documentation](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.Client.exec)), including: +The `Container.exec` method has various options (see [full API documentation](ops.pebble.Client.exec)), including: * `environment`: a dict of environment variables to pass to the process * `working_dir`: working directory to run the command in @@ -733,7 +733,7 @@ process.wait_output() ### Use input/output options -The simplest way of receiving standard output and standard error is by using the [`ExecProcess.wait_output`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecProcess.wait_output) method as shown below. The simplest way of sending standard input to the program is as a string, using the `stdin` parameter to `exec`. For example: +The simplest way of receiving standard output and standard error is by using the [`ExecProcess.wait_output`](ops.pebble.ExecProcess.wait_output) method as shown below. The simplest way of sending standard input to the program is as a string, using the `stdin` parameter to `exec`. For example: ```python process = container.exec(['tr', 'a-z', 'A-Z'], @@ -806,7 +806,7 @@ Caution: it's easy to get threading wrong and cause deadlocks, so it's best to u ### Send signals to a running command -To send a signal to the running process, use [`ExecProcess.send_signal`](https://ops.readthedocs.io/en/latest/reference/pebble.html#ops.pebble.ExecProcess.send_signal) with a signal number or name. For example, the following will terminate the "sleep 10" process after one second: +To send a signal to the running process, use [`ExecProcess.send_signal`](ops.pebble.ExecProcess.send_signal) with a signal number or name. For example, the following will terminate the "sleep 10" process after one second: ```python process = container.exec(['sleep', '10']) @@ -906,9 +906,9 @@ def test_backup_done(upload_fileobj):