How to register an interface

Also see:

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 with charm-relation-interfaces.


Expand to preview some example results

1. Clone (a fork of) the charm-relation-interfaces repo and set up an interface specification folder

git clone https://github.com/canonical/charm-relation-interfaces
cd /path/to/charm-relation-interfaces

2. Make a copy of the template folder

Copy the template folder to a new folder called the same as your interface (with underscores instead of dashes).

cp -r ./interfaces/__template__ ./interfaces/my_fancy_database

At this point you should see this directory structure:

# tree ./interfaces/my_fancy_database
./interfaces/my_fancy_database
└── v0
    ├── README.md
    ├── interface.yaml
    ├── interface_tests
    └── schema.py                                     
2 directories, 3 files

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

To verify that things work as they should, you can pip install pytest-interface-tester and then run interface_tester discover --include my_fancy_database from the charm-relation-interfaces root.

You should see:

- my_fancy_database:
  - v0:
   - provider:
     - <no tests>
     - schema OK
     - charms:
       - my_fancy_database_charm (https://github.com/your-github-slug/my-fancy-database-operator) custom_test_setup=no
   - requirer:
     - <no tests>
     - schema OK
     - <no charms>

In particular pay attention to schema. If it says NOT OK then there is something wrong with the pydantic model.

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 [\[Pydantic Schema\]](./schema.py)


### Requirer

The requirer publishes a list of tables to be created, as a json-encoded list of strings.

#### Example
\```yaml
application_data: {
   "tables": "['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. Add interface tests

See How to write interface tests.

7. Open a PR to the charm-relation-interfaces repo

Finally, open a pull request to the charm-relation-interfaces repo and drive it to completion, addressing any feedback or concerns that the maintainers may have.

Contributors: @ironcore864, @ppasotti