Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

docs: fix broken links and use cross references instead of absolute links #1519

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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 @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion docs/explanation/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**

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 @@ -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.

Expand Down
12 changes: 6 additions & 6 deletions docs/howto/manage-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def _on_snapshot(self, event: ops.ActionEvent):
...
```

> See more: [`ops.ActionEvent.params`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.ActionEvent.params)
> See more: [](ops.ActionEvent.params)

#### Report that an action has failed

Expand All @@ -105,7 +105,7 @@ def _on_snapshot(self, event: ops.ActionEvent):
...
```

> See more: [`ops.ActionEvent.fail`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.ActionEvent.fail)
> See more: [](ops.ActionEvent.fail)

#### Return the results of an action

Expand All @@ -117,7 +117,7 @@ def _on_snapshot(self, event: ops.ActionEvent):
event.set_results({'snapshot-size': size})
```

> See more: [`ops.ActionEvent.set_results`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.ActionEvent.set_results)
> See more: [](ops.ActionEvent.set_results)

#### Log the progress of an action

Expand All @@ -133,7 +133,7 @@ def _on_snapshot(self, event: ops.ActionEvent):
self.snapshot_table3()
```

> See more: [`ops.ActionEvent.log`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.ActionEvent.log))
> See more: [](ops.ActionEvent.log)

#### Record the ID of an action task

Expand All @@ -146,7 +146,7 @@ def _on_snapshot(self, event: ops.ActionEvent):
self.create_backup(temp_filename)
...
```
> See more: [`ops.ActionEvent.id`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.ActionEvent.id)
> See more: [](ops.ActionEvent.id)

## Test the feature

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/howto/manage-configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _on_config_changed(self, event):
self._update_layer_and_restart(None)
```

> See more: [`ops.CharmBase.config`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.CharmBase.config)
> See more: [](ops.CharmBase.config)

```{caution}

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 @@ -15,7 +15,7 @@ In the `src/charm.py` file, in the `__init__` function of your charm, set up an
self.framework.observe(self.on.leader_elected, self._on_leader_elected)
```

> See more: [`ops.LeaderElectedEvent`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.LeaderElectedEvent)
> See more: [](ops.LeaderElectedEvent)

Now, in the body of the charm definition, define the event handler. For example, the handler below will update a configuration file:

Expand Down
22 changes: 11 additions & 11 deletions docs/howto/manage-relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ Make sure to consult [the `charm-relations-interfaces` repository](https://githu
Make sure to add your interface to [the `charm-relations-interfaces` repository](https://github.com/canonical/charm-relation-interfaces).
```

To exchange data with other units of the same charm, define one or more `peers` endpoints including an interface name for each. Each peer relation must have an endpoint, which your charm will use to refer to the relation (as [`ops.Relation.name`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Relation.name)).
To exchange data with other units of the same charm, define one or more `peers` endpoints including an interface name for each. Each peer relation must have an endpoint, which your charm will use to refer to the relation (as [](ops.Relation.name)).

```yaml
peers:
replicas:
interface: charm_gossip
```

To exchange data with another charm, define a `provides` or `requires` endpoint including an interface name. By convention, the interface name should be unique in the ecosystem. Each relation must have an endpoint, which your charm will use to refer to the relation (as [`ops.Relation.name`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Relation.name)).
To exchange data with another charm, define a `provides` or `requires` endpoint including an interface name. By convention, the interface name should be unique in the ecosystem. Each relation must have an endpoint, which your charm will use to refer to the relation (as [](ops.Relation.name)).

```yaml
provides:
Expand Down Expand Up @@ -98,9 +98,9 @@ def _on_db_relation_created(self, event: ops.RelationCreatedEvent):
event.relation.data[event.app].update(credentials)
```

The event object that is passed to the handler has a `relation` property, which contains an [`ops.Relation`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Relation) object. Your charm uses this object to find out about the relation (such as which units are included, in the [`.units` attribute](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Relation.units), or whether the relation is broken, in the [`.active` attribute](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Relation.active)) and to get and set data in the relation databag.
The event object that is passed to the handler has a `relation` property, which contains an [](ops.Relation) object. Your charm uses this object to find out about the relation (such as which units are included, in the [`.units` attribute](ops.Relation.units), or whether the relation is broken, in the [`.active` attribute](ops.Relation.active)) and to get and set data in the relation databag.

> See more: [`ops.RelationCreatedEvent`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.RelationCreatedEvent)
> See more: [](ops.RelationCreatedEvent)

To do additional setup work when each unit joins the relation (both when the charms are first integrated and when additional units are added to the charm), your charm will need to observe the `relation-joined` event. In the `src/charm.py` file, in the `__init__` function of your charm, set up `relation-joined` event observers for the relevant relations and pair those with an event handler. For example:

Expand All @@ -116,7 +116,7 @@ def _on_smtp_relation_joined(self, event: ops.RelationJoinedEvent):
event.relation.data[event.unit]["smtp_credentials"] = smtp_credentials_secret_id
```

> See more: [`ops.RelationJoinedEvent`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.RelationJoinedEvent)
> See more: [](ops.RelationJoinedEvent)

##### Exchange data with other units

Expand All @@ -126,9 +126,9 @@ 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`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.RelationChangedevent), [`juju` | Relation (integration)](https://juju.is/docs/juju/relation#heading--permissions-around-relation-databags)
> See more: [](ops.RelationChangedEvent), [`juju` | Relation (integration)](https://juju.is/docs/juju/relation#heading--permissions-around-relation-databags)

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`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Model.get_relation) method or [`ops.Model.relations`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Model.relations) attribute.
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`

Expand Down Expand Up @@ -171,7 +171,7 @@ def _update_configuration(self, _: ops.Eventbase):

##### Exchange data across the various relations

To add data to the relation databag, use the [`.data` attribute](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Relation.data) much as you would a dictionary, after selecting whether to write to the app databag (leaders only) or unit databag. For example, to copy a value from the charm config to the relation data:
To add data to the relation databag, use the [`.data` attribute](ops.Relation.data) much as you would a dictionary, after selecting whether to write to the app databag (leaders only) or unit databag. For example, to copy a value from the charm config to the relation data:

```python
def _on_config_changed(self, event: ops.ConfigChangedEvent):
Expand Down Expand Up @@ -238,7 +238,7 @@ def _on_smtp_relation_departed(self, event: ops.RelationDepartedEvent):
self.remove_smtp_user(event.unit.name)
```

> See more: [ops.RelationDepartedEvent](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.RelationDepartedEvent)
> See more: [](ops.RelationDepartedEvent)

To clean up after a relation is entirely removed, have your charm observe the `relation-broken` event. In the `src/charm.py` file, in the `__init__` function of your charm, set up `relation-broken` events for the relevant relations and pair those with an event handler. For example:

Expand All @@ -255,7 +255,7 @@ def _on_db_relation_broken(self, event: ops.RelationBrokenEvent):
self.drop_database(event.app.name)
```

> See more: [ops.RelationBrokenEvent](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.RelationBrokenEvent)
> See more: [](ops.RelationBrokenEvent)

## Test the feature

Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions docs/howto/manage-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ resources:
description: test resource
```

In your charm's `src/charm.py` you can now use [`Model.resources.fetch(<resource_name>)`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Resources.fetch) to get the path to the resource, then manipulate it as needed. For example:
In your charm's `src/charm.py` you can now use [`Model.resources.fetch(<resource_name>)`](ops.Resources.fetch) to get the path to the resource, then manipulate it as needed. For example:

```python
# ...
Expand Down Expand Up @@ -59,7 +59,7 @@ def _on_config_changed(self, event):
# do something
```

The [`fetch()`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Resources.fetch) method will raise a [`NameError`](https://docs.python.org/3/library/exceptions.html#NameError) if the resource does not exist, and returns a Python [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path) object to the resource if it does.
The [`fetch()`](ops.Resources.fetch) method will raise a [`NameError`](https://docs.python.org/3/library/exceptions.html#NameError) if the resource does not exist, and returns a Python [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path) object to the resource if it does.

Note: During development, it may be useful to specify the resource at deploy time to facilitate faster testing without the need to publish a new charm/resource in between minor fixes. In the below snippet, we create a simple file with some text content, and pass it to the Juju controller to use in place of any published `my-resource` resource:

Expand Down
14 changes: 7 additions & 7 deletions docs/howto/manage-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Note that:
- The only data shared in plain text is the secret ID (a locator URI). The secret ID can be publicly shared. Juju will ensure that only remote apps/units to which the secret has explicitly been granted by the owner will be able to fetch the actual secret payload from that ID.
- The secret needs to be granted to a remote entity (app or unit), and that always goes via a relation instance. By passing a relation to `grant` (in this case the event's relation), we are explicitly declaring the scope of the secret -- its lifetime will be bound to that of this relation instance.

> See more: [`ops.Application.add_secret()`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Application.add_secret)
> See more: [](ops.Application.add_secret)

### Create a new secret revision

Expand Down Expand Up @@ -249,7 +249,7 @@ Note that:
- The observer charm gets a secret via the model (not its app/unit). Because it's the owner who decides who the secret is granted to, the ownership of a secret is not an observer concern. The observer code can rightfully assume that, so long as a secret ID is shared with it, the owner has taken care to grant and scope the secret in such a way that the observer has the rights to inspect its contents.
- The charm first gets the secret object from the model, then gets the secret's content (a dict) and accesses individual attributes via the dict's items.

> See more: [`ops.Secret.get_content()`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Secret.get_content)
> See more: [](ops.Secret.get_content)

### Label the secrets you're observing

Expand Down Expand Up @@ -310,17 +310,17 @@ So, having labelled the secret on creation, the database charm could add a new r
secret.set_content(...) # pass a new revision payload, as before
```

> See more: [`ops.Model.get_secret()`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Model.get_secret)
> See more: [](ops.Model.get_secret)

#### When to use labels

When should you use labels? A label is basically the secret's *name* (local to the charm), so whenever a charm has, or is observing, multiple secrets you should label them. This allows you to distinguish between secrets, for example, in the `SecretChangedEvent` shown above.

Most charms that use secrets have a fixed number of secrets each with a specific meaning, so the charm author should give them meaningful labels like `database-credential`, `tls-cert`, and so on. Think of these as "pets" with names.

In rare cases, however, a charm will have a set of secrets all with the same meaning: for example, a set of TLS certificates that are all equally valid. In this case it doesn't make sense to label them -- think of them as "cattle". To distinguish between secrets of this kind, you can use the [`Secret.unique_identifier`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Secret.unique_identifier) property.
In rare cases, however, a charm will have a set of secrets all with the same meaning: for example, a set of TLS certificates that are all equally valid. In this case it doesn't make sense to label them -- think of them as "cattle". To distinguish between secrets of this kind, you can use the [`Secret.unique_identifier`](ops.Secret.unique_identifier) property.

Note that [`Secret.id`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Secret.id), despite the name, is not really a unique ID, but a locator URI. We call this the "secret ID" throughout Juju and in the original secrets specification -- it probably should have been called "uri", but the name stuck.
Note that [`Secret.id`](ops.Secret.id), despite the name, is not really a unique ID, but a locator URI. We call this the "secret ID" throughout Juju and in the original secrets specification -- it probably should have been called "uri", but the name stuck.

### Peek at a new secret revision

Expand All @@ -336,7 +336,7 @@ Sometimes, before reconfiguring to use a new credential revision, the observer c
...
```

> See more: [`ops.Secret.peek_content()`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Secret.peek_content)
> See more: [](ops.Secret.peek_content)

### Start tracking a different secret revision

Expand All @@ -356,7 +356,7 @@ class MyWebserverCharm(ops.CharmBase):
self._configure_db_credentials(content['username'], content['password'])
```

> See more: [`ops.Secret.get_content()`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.Secret.get_content)
> See more: [](ops.Secret.get_content)

<br>

Expand Down
6 changes: 3 additions & 3 deletions docs/howto/manage-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,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`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.StorageAttachedEvent), [Juju SDK | Holistic vs delta charms](https://juju.is/docs/sdk/holistic-vs-delta-charms)
> See more: [](ops.StorageAttachedEvent), [Juju SDK | Holistic vs delta charms](https://juju.is/docs/sdk/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 Expand Up @@ -83,7 +83,7 @@ In the `src/charm.py` file, in the `__init__` function of your charm, set up an
self.framework.observe(self.on.cache_storage_detaching, self._on_storage_detaching)
```

> See more: [`ops.StorageDetachingEvent`](https://ops.readthedocs.io/en/latest/reference/ops.html#ops.StorageDetachingEvent)
> See more: [](ops.StorageDetachingEvent)

Now, in the body of the charm definition, define the event handler, or adjust an existing holistic one. For example, to warn users that data won't be cached:

Expand Down Expand Up @@ -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

Expand Down
Loading
Loading