How to manage interfaces¶
Register an interface¶
Suppose you have determined that you need to create a new relation interface called my_fancy_database.
Suppose that your interface specification has the following data model:
the requirer app is supposed to forward a list of tables that it wants to be provisioned by the database provider
the provider app (the database) at that point will reply with an API endpoint and, for each replica, it will provide a separate secret ID to authenticate the requests
These are the steps you need to take in order to register it in the charmlibs monorepo.
1. Clone (a fork of) the charmlibs repo¶
git clone https://github.com/canonical/charmlibs
cd /path/to/charmlibs
2. Create the interface directory¶
Registering an interface means adding its definition to charmlibs; you don’t need to publish a Python package to do that. Create a directory for the interface under interfaces/, named with the canonical interface name as it appears in charmcraft.yaml files – for example my_fancy_database – and add the three files that make up the definition:
mkdir -p ./interfaces/my_fancy_database
touch ./interfaces/my_fancy_database/{README.md,interface.yaml,schema.py}
Note
If you also want to ship a charm library implementing the interface, run just init --interface from the repository root instead, and answer the prompts. That scaffolds a full charmlibs.interfaces.<name> package, which is published to PyPI. See the charmlibs documentation for how to develop and release one.
3. Edit interface.yaml¶
Add to interface.yaml the charm that owns the reference implementation of the my_fancy_database interface. Assuming your my_fancy_database_charm plays the provider role in the interface, your interface.yaml will look like this:
# interface.yaml
providers:
- name: my-fancy-database-operator # same as metadata.yaml's .name
url: https://github.com/your-github-slug/my-fancy-database-operator
4. Edit schema.py¶
Edit schema.py to contain:
# schema.py
from interface_tester.schema_base import DataBagSchema
from pydantic import BaseModel, AnyHttpUrl, Field, Json
import typing
class ProviderUnitData(BaseModel):
secret_id: str = Field(
description='Secret ID for the key you need in order to query this unit.',
title='Query key secret ID',
examples=['secret:12312323112313123213'],
)
class ProviderAppData(BaseModel):
api_endpoint: AnyHttpUrl = Field(
description="URL to the database's endpoint.",
title='Endpoint API address',
examples=['https://example.com/v1/query'],
)
class ProviderSchema(DataBagSchema):
app: ProviderAppData
unit: ProviderUnitData
class RequirerAppData(BaseModel):
tables: Json[typing.List[str]] = Field(
description='Tables that the requirer application needs.',
title='Requested tables.',
examples=[['users', 'passwords']],
)
class RequirerSchema(DataBagSchema):
app: RequirerAppData
# we can omit `unit` because the requirer makes no use of the unit databags
DataBagSchema currently comes from pytest-interface-tester; charmlibs intends to replace it, so check the schemas of existing interfaces for the current base class before you write yours.
5. Edit README.md¶
Edit the README.md file to contain:
# `my_fancy_database`
## Overview
This relation interface describes the expected behavior between of any charm claiming to be able to interface with a Fancy Database and the Fancy Database itself.
Other Fancy Database-compatible providers can be used interchangeably as well.
## Usage
Typically, you can use the implementation of this interface from [this charm library](https://github.com/your_org/my_fancy_database_operator/blob/main/lib/charms/my_fancy_database/v0/fancy.py), although charm developers are free to provide alternative libraries as long as they comply with this interface specification.
## Direction
The `my_fancy_database` interface implements a provider/requirer pattern.
The requirer is a charm that wishes to act as a Fancy Database Service consumer, and the provider is a charm exposing a Fancy Database (-compatible API).
/```mermaid
flowchart TD
Requirer -- tables --> Provider
Provider -- endpoint, access_keys --> Requirer
/```
## Behavior
The requirer and the provider must adhere to a certain set of criteria to be considered compatible with the interface.
### Requirer
- Is expected to publish a list of tables in the application databag
### Provide
- Is expected to publish an endpoint URL in the application databag
- Is expected to create and grant a Juju Secret containing the access key for each shard and publish its secret ID in the unit databags.
## Relation Data
See the {ref}`\[Pydantic Schema\] <12689md>`
### Requirer
The requirer publishes a list of tables to be created, as a json-encoded list of strings.
#### Example
\```yaml
application_data: {
"tables": "{ref}`'users', 'passwords']"
}
\```
### Provider
The provider publishes an endpoint url and access keys for each shard.
#### Example
\```
application_data: {
"api_endpoint": "https://foo.com/query"
},
units_data : {
"my_fancy_unit/0": {
"secret_id": "secret:12312321321312312332312323"
},
"my_fancy_unit/1": {
"secret_id": "secret:45646545645645645646545456"
}
}
\```
6. Open a PR to the charmlibs repo¶
Finally, open a pull request to the charmlibs repo and drive it to completion, addressing any feedback or concerns that the maintainers may have.
Example¶
For an example of a registered interface, see ingress:
As you can see from its
interface.yamlfile, thecanonical/traefik-k8s-operatorcharm plays the provider role in the interface.The schema of this interface is defined in
schema.py.You can find out more information about this interface in its
README.md.