How to manage resources¶
See also:
Implement the feature¶
A charm can require file or oci-image resources, defined in charmcraft.yaml. You’ll upload the resources to Charmhub as one of the charm publishing steps. Then when a user deploys your charm from Charmhub, the resources will be available to your charm code.
For example, suppose your charmcraft.yaml file contains this simple resource definition:
resources:
my-resource:
type: file
filename: somefile.txt
description: test resource
In your charm’s src/charm.py you can now use Model.resources.fetch(<resource_name>) to get the path to the resource, then manipulate it as needed. For example:
import logging
import ops
logger = logging.getLogger(__name__)
class MyCharm(ops.CharmBase):
def _on_config_changed(self, event: ops.ConfigChangedEvent):
# Get the path to the file resource named 'my-resource'.
try:
resource_path = self.model.resources.fetch('my-resource')
except NameError:
logger.exception('Resource my-resource is not declared.')
self.unit.status = ops.BlockedStatus(
"Resource 'my-resource' not found; did you forget to "
'declare it in charmcraft.yaml?'
)
return
except ops.ModelError:
logger.exception('Could not claim resource my-resource.')
self.unit.status = ops.BlockedStatus(
"Could not claim resource 'my-resource'; run "
'`juju debug-log` for more information'
)
return
with resource_path.open() as f:
content = f.read()
# Do something with the content.
fetch() raises NameError if the resource isn’t declared in charmcraft.yaml, and ops.ModelError if it is declared but Juju can’t provide it. Otherwise it returns a pathlib.Path pointing at the resource.
During development it’s often useful to specify the resource at deploy time, so that you can test a change without publishing a new charm or resource for every minor fix. In the snippet below, we create a file with some text content and pass it to the Juju controller to use in place of any published my-resource resource:
echo "TEST" > /tmp/somefile.txt
charmcraft pack
juju deploy ./my-charm.charm --resource my-resource=/tmp/somefile.txt
Test the feature¶
Write unit tests¶
See first: How to write unit tests for a charm
If your charm needs access to a resource, make it available with ops.testing.State.resources, passing an ops.testing.Resource for each one. For example, to make the my-resource file resource available:
import pathlib
from ops import testing
ctx = testing.Context(
MyCharm,
meta={
'name': 'julie',
'resources': {'my-resource': {'type': 'file'}},
},
)
resource = testing.Resource(name='my-resource', path='/path/to/somefile.txt')
with ctx(ctx.on.start(), testing.State(resources={resource})) as mgr:
path = mgr.charm.model.resources.fetch('my-resource')
assert path == pathlib.Path('/path/to/somefile.txt')
Write integration tests¶
See first: How to write integration tests for a charm
During development and testing, it’s useful to specify resource locations when deploying the charm.
The conventional place to specify resource locations for testing is the upstream-source field in charmcraft.yaml’s resources section:
import pathlib
import jubilant
import pytest
import yaml
METADATA = yaml.safe_load(pathlib.Path('./charmcraft.yaml').read_text())
@pytest.mark.juju_setup
def test_deploy(charm: pathlib.Path, juju: jubilant.Juju):
resources = {
name: res['upstream-source']
for name, res in METADATA['resources'].items()
}
juju.deploy(charm, resources=resources)
juju.wait(jubilant.all_active)
See also: jubilant.Juju.deploy
Examples: valkey-operator, kafka-k8s-operator
We recommend including the charm fixture (even though it’s not used) so that the test fails immediately if a .charm file isn’t available.