Skip to content

Commit

Permalink
docs: fix links that pointed to earlier Juju docs (canonical#1575)
Browse files Browse the repository at this point in the history
This PR replaces links to https://juju.is/docs/ by internal
cross-references to other Ops pages or intersphinx cross-references to
Juju pages (as appropriate).

**Not included in this PR**

- The link in
https://ops.readthedocs.io/en/latest/howto/get-started-with-charm-testing.html#charmcraft-profiles
that should point to the Charmcraft docs. I'll follow up with a separate
PR after canonical/charmcraft#2085 gets merged
into Charmcraft.

- Links in the code samples of the Kubernetes tutorial (for example, in
[Create a minimal Kubernetes
charm](https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.html)).
I think it would be better to tackle these separately when improving the
tutorial - @tonyandrewmeyer what do you think?
  • Loading branch information
dwilding authored Feb 19, 2025
1 parent d54a26a commit 0e565da
Show file tree
Hide file tree
Showing 15 changed files with 40 additions and 38 deletions.
2 changes: 1 addition & 1 deletion docs/explanation/holistic-vs-delta-charms.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ Only some events make sense to handle holistically. For example, `remove` is tri

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.
This is very closely related to [which events can be deferred](/explanation/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.

On the other hand, if an event cannot be deferred, the charm cannot handle it holistically. This applies to action "events", `stop`, `remove`, `secret-expired`, `secret-rotate`, and Ops-emitted events such as `collect-status`.
4 changes: 2 additions & 2 deletions docs/explanation/how-and-when-to-defer-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ If the charm encounters a temporary failure (such as working with a container or

Note that it’s important to consider that when the deferred handler is run again, the Juju context may not be exactly the same as it was when the event was first emitted, so the charm code needs to be aware of this.

If the temporary failure is because the workload is busy, and the charm is deployed to a Kubernetes sidecar controller, you might be able to avoid the defer using a [Pebble custom notice](https://juju.is/docs/sdk/interact-with-pebble#heading--use-custom-notices-from-the-workload-container). For example, if the code can’t continue because the workload is currently restarting, if you can have a post-completion hook for the restart that executes `pebble notify`, then you can ensure that the charm is ‘woken up’ at the right time to handle the work.
If the temporary failure is because the workload is busy, and the charm is deployed to a Kubernetes sidecar controller, you might be able to avoid the defer using a [Pebble custom notice](#use-custom-notices-from-the-workload-container). For example, if the code can’t continue because the workload is currently restarting, if you can have a post-completion hook for the restart that executes `pebble notify`, then you can ensure that the charm is ‘woken up’ at the right time to handle the work.

In the future, we hope to see a Juju ‘request re-emit event’ feature that will let the charm tell Juju when it expects the problem to be resolved.

Expand All @@ -33,7 +33,7 @@ In some situations, the charm is waiting for a system to be ready, but it’s no

Deferring the work here is ok, but it’s important to consider the delay between deferring the event and its eventual re-emitting - it’s not safe to assume that this will be a small period of time, unless you know that another event can be expected.

For a Kubernetes charm, If the charm is waiting on the workload and it’s possible to have the workload execute a command when it’s ready, then using a [Pebble custom notice](https://juju.is/docs/sdk/interact-with-pebble#heading--use-custom-notices-from-the-workload-container) is much better than deferring. This then becomes another example of “waiting for a collection of events”, described above.
For a Kubernetes charm, if the charm is waiting on the workload and it’s possible to have the workload execute a command when it’s ready, then using a [Pebble custom notice](#use-custom-notices-from-the-workload-container) is much better than deferring. This then becomes another example of “waiting for a collection of events”, described above.

## Not possible: actions, shutting down, framework generated events, secrets

Expand Down
2 changes: 1 addition & 1 deletion docs/howto/get-started-with-charm-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def test_pebble_ready_writes_config_file():
## Integration testing

Where unit testing focuses on black-box method-by-method verification, integration testing focuses on the big picture. Typically integration tests check that the charm does not break (generally this means: blocks with status `blocked` or `error`) when a (mocked) cloud admin performs certain operations. These operations are scripted by using, in order of abstraction:
- shell commands against [the `juju` cli](https://juju.is/docs/olm/juju-cli-commands)
- shell commands against [the `juju` CLI](inv:juju:std:label#list-of-juju-cli-commands)
- [`python-libjuju`](https://github.com/juju/python-libjuju), wrapping juju api calls
- [`pytest-operator`](https://github.com/charmed-kubernetes/pytest-operator), a `pytest` plugin wrapping `python-libjuju`
- [`zaza`](https://zaza.readthedocs.io/en/latest/index.html), a testing-framework-agnostic wrapper on top of `python-libjuju`
Expand Down
2 changes: 1 addition & 1 deletion docs/howto/manage-leadership-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def _on_leader_elected(self, event: ops.LeaderElectedEvent):
To have the leader notify other units about leadership changes, change data in a peer relation.

> See more: [Peer Relations](https://juju.is/docs/juju/relation#heading--peer)
> See more: {external+juju:ref}`Juju | Relation <relation>`
[note status="Use the peer relation rather than `leader-setting-changed`"]
In the past, this was done by observing a `leader-setting-changed` event, which is now deprecated.
Expand Down
4 changes: 3 additions & 1 deletion docs/howto/manage-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ class HelloOperatorCharm(ops.CharmBase):
> - [`logging.getLogger().info()`](https://docs.python.org/3/library/logging.html#logging.Logger.info)
> - [`logging.getLogger().debug()`](https://docs.python.org/3/library/logging.html#logging.Logger.debug)
Juju automatically picks up logs from charm code that uses the Python [logging facility](https://docs.python.org/3/library/logging.html), so we can use the Juju [`debug-log` command](https://juju.is/docs/juju/juju-debug-log) to display logs for a model. Note that it shows logs from the charm code (charm container), but not the workload container. Read ["Use `juju debug-log`"](https://juju.is/docs/sdk/get-logs-from-a-kubernetes-charm#heading--use-juju-debug-log) for more information.
Juju automatically picks up logs from charm code that uses the Python [logging facility](https://docs.python.org/3/library/logging.html), so we can use the Juju `debug-log` command to display logs for a model. Note that it shows logs from the charm code (charm container), but not the workload container.

Besides logs, `stderr` is also captured by Juju. So, if a charm generates a warning, it will also end up in Juju's debug log. This behaviour is consistent between K8s charms and machine charms.

> See more: {external+juju:ref}`Juju | How to manage logs <manage-logs>`
**Tips for good practice:**

- Note that some logging is performed automatically by the Juju controller, for example when an event handler is called. Try not to replicate this behaviour in your own code.
Expand Down
7 changes: 4 additions & 3 deletions docs/howto/manage-relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ requires:

Other than this, implement a subordinate relation in the same way as any other relation. Note however that subordinate units cannot see each other's peer data.

> See also: [Charm taxonomy](https://juju.is/docs/sdk/charm-taxonomy#heading--subordinate-charms)
> See also: {external+juju:ref}`Juju | Charm taxonomy <charm-taxonomy>`

### Add code to use a relation

Expand All @@ -75,6 +75,7 @@ In most cases, the charm library will handle observing the Juju relation events,

If you are developing your own interface - most commonly for charm-specific peer data exchange, then you will need to observe the Juju relation events and add appropriate handlers.

(set-up-a-relation)=
##### Set up a relation

To do initial setup work when a charm is first integrated with another charm (or, in the case of a peer relation, when a charm is first deployed) your charm will need to observe the relation-created event. For example, a charm providing a database relation might need to create the database and credentials, so that the requirer charm can use the database. In the `src/charm.py` file, in the `__init__` function of your charm, set up `relation-created` event observers for the relevant relations and pair those with an event handler.
Expand Down Expand Up @@ -123,11 +124,11 @@ To use data received through the relation, have your charm observe the `relation
framework.observe(self.on.replicas_relation_changed, self._update_configuration)
```

> See more: [](ops.RelationChangedEvent), [`juju` | Relation (integration)](https://juju.is/docs/juju/relation#heading--permissions-around-relation-databags)
> See more: [](ops.RelationChangedEvent), {external+juju:ref}`Juju | Relation (integration) <relation>`

Most of the time, you should use the same holistic handler as when receiving other data, such as `secret-changed` and `config-changed`. To access the relation(s) in your holistic handler, use the [](ops.Model.get_relation) method or [](ops.Model.relations) attribute.

> See also: {ref}`holistic-vs-delta-charms`
> See also: [](/explanation/holistic-vs-delta-charms)

If your change will have at most one relation on the endpoint, to get the `Relation` object use `Model.get_relation`; for example:

Expand Down
2 changes: 1 addition & 1 deletion docs/howto/manage-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ In the `src/charm.py` file, in the `__init__` function of your charm, set up an
self.framework.observe(self.on.cache_storage_attached, self._update_configuration)
```

> See more: [](ops.StorageAttachedEvent), [Juju SDK | Holistic vs delta charms](https://juju.is/docs/sdk/holistic-vs-delta-charms)
> See more: [](ops.StorageAttachedEvent), [](/explanation/holistic-vs-delta-charms)

Storage volumes will be automatically mounted into the charm container at either the path specified in the `location` field in the metadata, or the default location `/var/lib/juju/storage/<storage-name>`. However, your charm code should not hard-code the location, and should instead use the `.location` property of the storage object.

Expand Down
2 changes: 1 addition & 1 deletion docs/howto/manage-the-workload-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _on_start(self, event: ops.StartEvent):
## Test the feature

> See first: [Get started with charm testing](https://juju.is/docs/sdk/get-started-with-charm-testing)
> See first: {ref}`get-started-with-charm-testing`
You'll want to add unit and integration tests.

Expand Down
3 changes: 2 additions & 1 deletion docs/howto/run-workloads-with-a-charm-kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`](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.
To add a configuration layer, call [`Container.add_layer`](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.

Expand Down Expand Up @@ -823,6 +823,7 @@ Traceback (most recent call last):
ops.pebble.ExecError: non-zero exit code 143 executing ['sleep', '10']
```

(use-custom-notices-from-the-workload-container)=
## Use custom notices from the workload container

### Record a notice
Expand Down
3 changes: 0 additions & 3 deletions docs/howto/run-workloads-with-a-charm-machines.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
# How to run workloads with a charm - machines

There are several ways your charm might start a workload, depending on the type of charm you’re authoring.
<!--
Before writing the code to start your workload, recall the [Lifecycle events](https://juju.is/docs/sdk/events) section, and note that when the `start` event is emitted, charm authors should ensure their workloads are configured to "persist in a started state without further intervention from Juju or an administrator".
-->

For a machine charm, it is likely that packages will need to be fetched, installed and started to provide the desired charm functionality. This can be achieved by interacting with the system’s package manager, ensuring that package and service status is maintained by reacting to events accordingly.

Expand Down
6 changes: 4 additions & 2 deletions docs/howto/turn-a-hooks-based-charm-into-an-ops-charm.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ It is in our interest to move the handler logic for each `/hooks/<hook_name>` to
- We can avoid code duplication by accessing shared data via the CharmBase interface provided through `self`.
- The code is all in one place, easier to maintain.
- We automatically have one Python object we can test, instead of going back and forth between Bash scripts and Python wrappers.
- We can use [the awesome testing Harness](https://juju.is/docs/sdk/testing).

So let's do that.

Expand Down Expand Up @@ -206,7 +205,7 @@ That allows us to fetch the Relation wherever we need it and access its contents
)
```

Note how `relation.data` provides an interface to the relation databag (more on that [here](https://juju.is/docs/sdk/relations#heading--relation-data)) and we need to select which part of that bag to access by passing an `ops.model.Unit` instance.
Note how `relation.data` provides an interface to the relation databag (see [](#set-up-a-relation)) and we need to select which part of that bag to access by passing an `ops.model.Unit` instance.

#### Logging

Expand Down Expand Up @@ -285,6 +284,9 @@ Furthermore we can get rid of the `start` handler, since the `snap.ensure()` cal

The final result can be inspected at [this branch](https://github.com/PietroPasotti/hooks-to-ops/tree/3-py-final).

### Testing

After you've prepared the event handlers, you should write tests for the charm. See [](/howto/get-started-with-charm-testing).

## Closing notes

Expand Down
2 changes: 1 addition & 1 deletion docs/howto/write-unit-tests-for-a-charm.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Notably, specifying relations in `charmcraft.yaml` does not automatically make t
harness. If you have e.g. code that accesses relation data, you must manually add those relations
(including peer relations) for the harness to provide access to that relation data to your charm.

In some cases it may be useful to start the test harness and fire the same hooks that Juju would fire on deployment. This can be achieved using the `begin_with_initial_hooks()` method , to be used in place of the `begin()` method. This method will trigger the events: `install -> relation-created -> config-changed -> start -> relation-joined` depending on whether any relations have been created prior calling `begin_with_initial_hooks()`. An example of this is shown in the [testing relations](https://juju.is/docs/sdk/relations) section.
In some cases it may be useful to start the test harness and fire the same hooks that Juju would fire on deployment. This can be achieved using the `begin_with_initial_hooks()` method , to be used in place of the `begin()` method. This method will trigger the events: `install -> relation-created -> config-changed -> start -> relation-joined` depending on whether any relations have been created prior calling `begin_with_initial_hooks()`. An example of this is shown in [](ops.testing.Harness).

Using the `harness` variable, we can simulate various events in the charm’s lifecycle:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,7 @@ The diagram below illustrates the workflow for the case where the database relat

Now that the charm is getting more complex, there are many more cases where the unit status needs to be set. It's often convenient to do this in a more declarative fashion, which is where the collect-status event can be used.

<!-- TODO: this page doesn't belong in the Juju docs, it should be moved over to ops and this can be a local reference. -->
> Read more: [Events > Collect App Status and Collect Unit Status](https://juju.is/docs/sdk/events-collect-app-status-and-collect-unit-status)
> Read more: [](ops.CollectStatusEvent)
In your charm's `__init__` add a new observer:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@ In this chapter of the tutorial you will set up your development environment.

You will need a charm directory, the various tools in the charm SDK, Juju, and a Kubernetes cloud. And it’s a good idea if you can do all your work in an isolated development environment.

You can get all of this by following our generic development setup guide, with some annotations.

> See [Set up / tear down automatically](https://juju.is/docs/juju/set-up--tear-down-your-test-environment#set-up-tear-down-automatically), with the following changes:
> - At the directory step, call your directory `fastapi-demo`.
> - At the VM setup step, call your VM `charm-dev` and also set up Docker:
> 1. `sudo addgroup --system docker`
> 1. `sudo adduser $USER docker`
> 1. `newgrp docker`
> 1. `sudo snap install docker`.
> - At the cloud selection step, choose `microk8s`.
> - At the mount step: Make sure to read the box with tips on how to edit files locally while running them inside the VM! <br><br>
> All set!


Congratulations, your development environment is now ready!
To set all of this up, see {external+juju:ref}`Juju | Manage your deployment environment > Set things up <set-things-up>`, with the following changes:

- At the directory step, call your directory `fastapi-demo`.
- At the VM setup step, call your VM `charm-dev`. Also set up Docker:
```text
sudo addgroup --system docker
sudo adduser $USER docker
newgrp docker
sudo snap install docker
```
- At the cloud selection step, choose `microk8s`.
- At the mount step, read the tips about how to edit files locally while running them inside the VM.
Congratulations, your development environment is ready!
> **See next: {ref}`Create a minimal Kubernetes charm <create-a-minimal-kubernetes-charm>`**
6 changes: 4 additions & 2 deletions docs/tutorial/write-your-first-machine-charm.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ The application has other features that we can exploit, but for now this is enou

## Set up your development environment

> See more: {external+juju:ref}`Juju | Manage your deployment environment > Automatically <manage-your-deployment-environment>` 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 {external+juju:ref}`Juju | Manage your deployment environment > Set things up <set-things-up>` 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}
Expand Down Expand Up @@ -522,7 +524,7 @@ Congratulations, your charm user can view the version of the workload deployed f

## 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 {external+juju:ref}`Juju | Manage your deployment environment > Tear things down <tear-things-down>`.



Expand Down

0 comments on commit 0e565da

Please sign in to comment.