dpcharmlibs-interfaces 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dpcharmlibs_interfaces-1.0.0/.gitignore +184 -0
- dpcharmlibs_interfaces-1.0.0/CHANGELOG.md +3 -0
- dpcharmlibs_interfaces-1.0.0/PKG-INFO +266 -0
- dpcharmlibs_interfaces-1.0.0/README.md +249 -0
- dpcharmlibs_interfaces-1.0.0/pyproject.toml +97 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/__init__.py +434 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/_version.py +15 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/diff.py +94 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/errors.py +34 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/events.py +333 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/handlers.py +1142 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/models.py +518 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/py.typed +0 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/repository.py +521 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/repository_interfaces.py +210 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/secrets.py +202 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/types.py +61 -0
- dpcharmlibs_interfaces-1.0.0/src/dpcharmlibs/interfaces/utils.py +67 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/__init__.py +13 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/actions.yaml +4 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/actions.yaml +78 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/charmcraft.yaml +25 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/config.yaml +5 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/metadata.yaml +40 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/pyproject.toml +18 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/src/charm.py +875 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/application-charm/src/etcd_requires.py +176 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/actions.yaml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/charmcraft.yaml +12 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/lib/charms/data_platform_libs/v0/data_interfaces.py +5782 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/metadata.yaml +12 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/pyproject.toml +14 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/src/charm.py +65 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/backward-compatibility-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/common.py +40 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/actions.yaml +175 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/charmcraft.yaml +24 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/metadata.yaml +34 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/pyproject.toml +16 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/src/charm.py +523 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/database-charm/src/status-schema.json +26 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/actions.yaml +128 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/charmcraft.yaml +23 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/metadata.yaml +34 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/pyproject.toml +18 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/src/charm.py +231 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/dummy-database-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/actions.yaml +27 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/charmcraft.yaml +12 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/metadata.yaml +16 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/pyproject.toml +14 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/src/charm.py +231 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/actions.yaml +15 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/charmcraft.yaml +12 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/metadata.yaml +16 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/pyproject.toml +14 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/src/charm.py +169 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/kafka-connect-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/actions.yaml +6 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/charmcraft.yaml +12 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/library/README.md +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/library/pyproject.toml +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/metadata.yaml +16 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/pyproject.toml +14 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/src/charm.py +172 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/charms/opensearch-charm/src/common.py +1 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/conftest.py +254 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/helpers.py +300 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/jubilant_helpers.py +144 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/pack.sh +28 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/scripts/install_etcdctl.sh +11 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_backward_compatibility_charm.py +87 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_charm.py +1324 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_etcd_charm.py +217 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_kafka_charm.py +205 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_kafka_connect_charm.py +152 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_opensearch_charm.py +132 -0
- dpcharmlibs_interfaces-1.0.0/tests/integration/test_status.py +176 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/__init__.py +13 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/conftest.py +55 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/helpers.py +75 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/test_data_interfaces.py +1780 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/test_status.py +234 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/test_version.py +21 -0
- dpcharmlibs_interfaces-1.0.0/tests/unit/test_version_in_charm.py +38 -0
- dpcharmlibs_interfaces-1.0.0/uv.lock +1688 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
cos-tool*
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.coverage-*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
.report/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
#Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# UV
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
#uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
110
|
+
#poetry.lock
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
#pdm.lock
|
|
115
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
116
|
+
# in version control.
|
|
117
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
118
|
+
.pdm.toml
|
|
119
|
+
.pdm-python
|
|
120
|
+
.pdm-build/
|
|
121
|
+
|
|
122
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
123
|
+
__pypackages__/
|
|
124
|
+
|
|
125
|
+
# Celery stuff
|
|
126
|
+
celerybeat-schedule
|
|
127
|
+
celerybeat.pid
|
|
128
|
+
|
|
129
|
+
# SageMath parsed files
|
|
130
|
+
*.sage.py
|
|
131
|
+
|
|
132
|
+
# Environments
|
|
133
|
+
.env
|
|
134
|
+
.venv
|
|
135
|
+
env/
|
|
136
|
+
venv/
|
|
137
|
+
ENV/
|
|
138
|
+
env.bak/
|
|
139
|
+
venv.bak/
|
|
140
|
+
|
|
141
|
+
# Spyder project settings
|
|
142
|
+
.spyderproject
|
|
143
|
+
.spyproject
|
|
144
|
+
|
|
145
|
+
# Rope project settings
|
|
146
|
+
.ropeproject
|
|
147
|
+
|
|
148
|
+
# mkdocs documentation
|
|
149
|
+
/site
|
|
150
|
+
|
|
151
|
+
# mypy
|
|
152
|
+
.mypy_cache/
|
|
153
|
+
.dmypy.json
|
|
154
|
+
dmypy.json
|
|
155
|
+
|
|
156
|
+
# Pyre type checker
|
|
157
|
+
.pyre/
|
|
158
|
+
|
|
159
|
+
# pytype static type analyzer
|
|
160
|
+
.pytype/
|
|
161
|
+
|
|
162
|
+
# Cython debug symbols
|
|
163
|
+
cython_debug/
|
|
164
|
+
|
|
165
|
+
# PyCharm
|
|
166
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
167
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
168
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
169
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
170
|
+
.idea/
|
|
171
|
+
|
|
172
|
+
# PyPI configuration file
|
|
173
|
+
.pypirc
|
|
174
|
+
|
|
175
|
+
# packed charms
|
|
176
|
+
.packed
|
|
177
|
+
|
|
178
|
+
# temporary directory for packing charms
|
|
179
|
+
.tmp
|
|
180
|
+
|
|
181
|
+
# file for storing tests.
|
|
182
|
+
.collected
|
|
183
|
+
|
|
184
|
+
!**/charms/*/lib/
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dpcharmlibs-interfaces
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The dpcharmlibs.interfaces package.
|
|
5
|
+
Project-URL: Repository, https://github.com/canonical/data-platform-charmlibs
|
|
6
|
+
Project-URL: Issues, https://github.com/canonical/data-platform-charmlibs/issues
|
|
7
|
+
Author: The Data Platform team at Canonical Ltd.
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: ops<4,>=3
|
|
15
|
+
Requires-Dist: pydantic<3,>=2.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# dpcharmlibs.interfaces: Library to manage the relation for the data-platform products
|
|
19
|
+
|
|
20
|
+
The `interfaces` library.
|
|
21
|
+
|
|
22
|
+
To install, add `dpcharmlibs-interfaces` to your Python dependencies. Then in your Python code, import as:
|
|
23
|
+
|
|
24
|
+
```py
|
|
25
|
+
from dpcharmlibs import interfaces
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Abstract
|
|
29
|
+
|
|
30
|
+
This V1 has been specified in
|
|
31
|
+
<https://docs.google.com/document/d/1lnuonWnoQb36RWYwfHOBwU0VClLbawpTISXIC_yNKYo>,
|
|
32
|
+
and should be backward compatible with v0 clients.
|
|
33
|
+
|
|
34
|
+
This library contains the Requires and Provides classes for handling the relation
|
|
35
|
+
between an application and multiple managed application supported by the data-team:
|
|
36
|
+
MySQL, Postgresql, MongoDB, Redis, Kafka, and Karapace.
|
|
37
|
+
|
|
38
|
+
## Components
|
|
39
|
+
|
|
40
|
+
### Models
|
|
41
|
+
|
|
42
|
+
This library exposes basic default models that can be used in most cases.
|
|
43
|
+
If you need more complex models, you can subclass them.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from dpcharmlibs.interfaces import RequirerCommonModel, ExtraSecretStr
|
|
47
|
+
|
|
48
|
+
class ExtendedCommonModel(RequirerCommonModel):
|
|
49
|
+
operator_password: ExtraSecretStr
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Secret groups are handled using annotated types.
|
|
53
|
+
If you wish to add extra secret groups, please follow the following model.
|
|
54
|
+
The string metadata represents the secret group name, and `OptionalSecretStr` is a TypeAlias for
|
|
55
|
+
`SecretStr | None`. Finally, `SecretStr` represents a field validating the URI pattern `secret:.*`
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
MyGroupSecretStr = Annotated[OptionalSecretStr, Field(exclude=True, default=None), "mygroup"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Fields not specified as OptionalSecretStr and extended with a group name in the metadata will NOT
|
|
62
|
+
get serialised.
|
|
63
|
+
|
|
64
|
+
### Requirer Charm
|
|
65
|
+
|
|
66
|
+
This library is a uniform interface to a selection of common database
|
|
67
|
+
metadata, with added custom events that add convenience to database management,
|
|
68
|
+
and methods to consume the application related data.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from dpcharmlibs.interfaces import (
|
|
72
|
+
RequirerCommonModel,
|
|
73
|
+
RequirerDataContractV1,
|
|
74
|
+
ResourceCreatedEvent,
|
|
75
|
+
ResourceEntityCreatedEvent,
|
|
76
|
+
ResourceProviderModel,
|
|
77
|
+
ResourceRequirerEventHandler,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
class ClientCharm(CharmBase):
|
|
81
|
+
# Database charm that accepts connections from application charms.
|
|
82
|
+
def __init__(self, *args) -> None:
|
|
83
|
+
super().__init__(*args)
|
|
84
|
+
|
|
85
|
+
requests = [
|
|
86
|
+
RequirerCommonModel(
|
|
87
|
+
resource="clientdb",
|
|
88
|
+
),
|
|
89
|
+
RequirerCommonModel(
|
|
90
|
+
resource="clientbis",
|
|
91
|
+
),
|
|
92
|
+
RequirerCommonModel(
|
|
93
|
+
entity_type="USER",
|
|
94
|
+
)
|
|
95
|
+
]
|
|
96
|
+
self.database = ResourceRequirerEventHandler(
|
|
97
|
+
self,"database", requests, response_model=ResourceProviderModel
|
|
98
|
+
)
|
|
99
|
+
self.framework.observe(self.database.on.resource_created, self._on_resource_created)
|
|
100
|
+
self.framework.observe(self.database.on.resource_entity_created, self._on_entity_created)
|
|
101
|
+
|
|
102
|
+
def _on_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
103
|
+
# Event triggered when a new database is created.
|
|
104
|
+
relation_id = event.relation.id
|
|
105
|
+
response = event.response # This is the response model
|
|
106
|
+
|
|
107
|
+
username = event.response.username
|
|
108
|
+
password = event.response.password
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
def _on_entity_created(self, event: ResourceCreatedEvent) -> None:
|
|
112
|
+
# Event triggered when a new entity is created.
|
|
113
|
+
...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Compared to V0, this library makes heavy use of pydantic models, and allows for
|
|
117
|
+
multiple requests, specified as a list.
|
|
118
|
+
On the Requirer side, each response will trigger one custom event for that response.
|
|
119
|
+
This way, it allows for more strategic events to be emitted according to the request.
|
|
120
|
+
|
|
121
|
+
As show above, the library provides some custom events to handle specific situations,
|
|
122
|
+
which are listed below:
|
|
123
|
+
|
|
124
|
+
- resource_created: event emitted when the requested database is created.
|
|
125
|
+
- resource_entity_created: event emitted when the requested entity is created.
|
|
126
|
+
- endpoints_changed: event emitted when the read/write endpoints of
|
|
127
|
+
the database have changed.
|
|
128
|
+
- read_only_endpoints_changed: event emitted when the read-only endpoints of
|
|
129
|
+
the database have changed. Event is not triggered if read/write endpoints
|
|
130
|
+
changed too.
|
|
131
|
+
|
|
132
|
+
If it is needed to connect multiple database clusters to the same relation endpoint
|
|
133
|
+
the application charm can implement the same code as if it would connect to only
|
|
134
|
+
one database cluster (like the above code example).
|
|
135
|
+
|
|
136
|
+
To differentiate multiple clusters connected to the same relation endpoint
|
|
137
|
+
the application charm can use the name of the remote application:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
def _on_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
141
|
+
# Get the remote app name of the cluster that triggered this event
|
|
142
|
+
cluster = event.relation.app.name
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
It is also possible to provide an alias for each different database cluster/relation.
|
|
146
|
+
|
|
147
|
+
So, it is possible to differentiate the clusters in two ways.
|
|
148
|
+
|
|
149
|
+
The first is to use the remote application name, with `event.relation.app.name`.
|
|
150
|
+
|
|
151
|
+
The second way is to use different event handlers to handle each cluster events.
|
|
152
|
+
|
|
153
|
+
The implementation would be something like the following code:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from dpcharmlibs.interfaces import (
|
|
157
|
+
RequirerCommonModel,
|
|
158
|
+
RequirerDataContractV1,
|
|
159
|
+
ResourceCreatedEvent,
|
|
160
|
+
ResourceEntityCreatedEvent,
|
|
161
|
+
ResourceProviderModel,
|
|
162
|
+
ResourceRequirerEventHandler,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
class ApplicationCharm(CharmBase):
|
|
166
|
+
# Application charm that connects to database charms.
|
|
167
|
+
|
|
168
|
+
def __init__(self, *args):
|
|
169
|
+
super().__init__(*args)
|
|
170
|
+
|
|
171
|
+
requests = [
|
|
172
|
+
RequirerCommonModel(
|
|
173
|
+
resource="clientdb",
|
|
174
|
+
),
|
|
175
|
+
RequirerCommonModel(
|
|
176
|
+
resource="clientbis",
|
|
177
|
+
),
|
|
178
|
+
]
|
|
179
|
+
# Define the cluster aliases and one handler for each cluster database
|
|
180
|
+
# created event.
|
|
181
|
+
self.database = ResourceRequirerEventHandler(
|
|
182
|
+
self,
|
|
183
|
+
relation_name="database"
|
|
184
|
+
relations_aliases = ["cluster1", "cluster2"],
|
|
185
|
+
requests=
|
|
186
|
+
)
|
|
187
|
+
self.framework.observe(
|
|
188
|
+
self.database.on.cluster1_resource_created, self._on_cluster1_resource_created
|
|
189
|
+
)
|
|
190
|
+
self.framework.observe(
|
|
191
|
+
self.database.on.cluster2_resource_created, self._on_cluster2_resource_created
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def _on_cluster1_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
195
|
+
# Handle the created database on the cluster named cluster1
|
|
196
|
+
|
|
197
|
+
# Create configuration file for app
|
|
198
|
+
config_file = self._render_app_config_file(
|
|
199
|
+
event.response.username,
|
|
200
|
+
event.response.password,
|
|
201
|
+
event.response.endpoints,
|
|
202
|
+
)
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
def _on_cluster2_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
206
|
+
# Handle the created database on the cluster named cluster2
|
|
207
|
+
|
|
208
|
+
# Create configuration file for app
|
|
209
|
+
config_file = self._render_app_config_file(
|
|
210
|
+
event.response.username,
|
|
211
|
+
event.response.password,
|
|
212
|
+
event.response.endpoints,
|
|
213
|
+
)
|
|
214
|
+
...
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Provider Charm
|
|
218
|
+
|
|
219
|
+
Following an example of using the ResourceRequestedEvent, in the context of the
|
|
220
|
+
database charm code:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from dpcharmlibs.interfaces import (
|
|
224
|
+
ResourceProviderEventHandler,
|
|
225
|
+
ResourceProviderModel,
|
|
226
|
+
ResourceRequestedEvent,
|
|
227
|
+
RequirerCommonModel,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
class SampleCharm(CharmBase):
|
|
231
|
+
|
|
232
|
+
def __init__(self, *args):
|
|
233
|
+
super().__init__(*args)
|
|
234
|
+
# Charm events defined in the database provides charm library.
|
|
235
|
+
self.provided_database = ResourceProviderEventHandler(
|
|
236
|
+
self, "database", RequirerCommonModel,
|
|
237
|
+
)
|
|
238
|
+
self.framework.observe(self.provided_database.on.resource_requested,
|
|
239
|
+
self._on_resource_requested)
|
|
240
|
+
# Database generic helper
|
|
241
|
+
self.database = DatabaseHelper()
|
|
242
|
+
|
|
243
|
+
def _on_resource_requested(self, event: ResourceRequestedEvent) -> None:
|
|
244
|
+
# Handle the event triggered by a new database requested in the relation
|
|
245
|
+
# Retrieve the database name using the charm library.
|
|
246
|
+
db_name = event.request.resource
|
|
247
|
+
# generate a new user credential
|
|
248
|
+
username = self.database.generate_user(event.request.request_id)
|
|
249
|
+
password = self.database.generate_password(event.request.request_id)
|
|
250
|
+
# set the credentials for the relation
|
|
251
|
+
response = ResourceProviderModel(
|
|
252
|
+
salt=event.request.salt,
|
|
253
|
+
request_id=event.request.request_id,
|
|
254
|
+
resource=db_name,
|
|
255
|
+
username=username,
|
|
256
|
+
password=password,
|
|
257
|
+
...
|
|
258
|
+
)
|
|
259
|
+
self.provided_database.set_response(event.relation.id, response)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
As shown above, the library provides a custom event (resource_requested) to handle
|
|
263
|
+
the situation when an application charm requests a new database to be created.
|
|
264
|
+
It's preferred to subscribe to this event instead of relation changed event to avoid
|
|
265
|
+
creating a new database when other information other than a database name is
|
|
266
|
+
exchanged in the relation databag.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# dpcharmlibs.interfaces: Library to manage the relation for the data-platform products
|
|
2
|
+
|
|
3
|
+
The `interfaces` library.
|
|
4
|
+
|
|
5
|
+
To install, add `dpcharmlibs-interfaces` to your Python dependencies. Then in your Python code, import as:
|
|
6
|
+
|
|
7
|
+
```py
|
|
8
|
+
from dpcharmlibs import interfaces
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Abstract
|
|
12
|
+
|
|
13
|
+
This V1 has been specified in
|
|
14
|
+
<https://docs.google.com/document/d/1lnuonWnoQb36RWYwfHOBwU0VClLbawpTISXIC_yNKYo>,
|
|
15
|
+
and should be backward compatible with v0 clients.
|
|
16
|
+
|
|
17
|
+
This library contains the Requires and Provides classes for handling the relation
|
|
18
|
+
between an application and multiple managed application supported by the data-team:
|
|
19
|
+
MySQL, Postgresql, MongoDB, Redis, Kafka, and Karapace.
|
|
20
|
+
|
|
21
|
+
## Components
|
|
22
|
+
|
|
23
|
+
### Models
|
|
24
|
+
|
|
25
|
+
This library exposes basic default models that can be used in most cases.
|
|
26
|
+
If you need more complex models, you can subclass them.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from dpcharmlibs.interfaces import RequirerCommonModel, ExtraSecretStr
|
|
30
|
+
|
|
31
|
+
class ExtendedCommonModel(RequirerCommonModel):
|
|
32
|
+
operator_password: ExtraSecretStr
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Secret groups are handled using annotated types.
|
|
36
|
+
If you wish to add extra secret groups, please follow the following model.
|
|
37
|
+
The string metadata represents the secret group name, and `OptionalSecretStr` is a TypeAlias for
|
|
38
|
+
`SecretStr | None`. Finally, `SecretStr` represents a field validating the URI pattern `secret:.*`
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
MyGroupSecretStr = Annotated[OptionalSecretStr, Field(exclude=True, default=None), "mygroup"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Fields not specified as OptionalSecretStr and extended with a group name in the metadata will NOT
|
|
45
|
+
get serialised.
|
|
46
|
+
|
|
47
|
+
### Requirer Charm
|
|
48
|
+
|
|
49
|
+
This library is a uniform interface to a selection of common database
|
|
50
|
+
metadata, with added custom events that add convenience to database management,
|
|
51
|
+
and methods to consume the application related data.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from dpcharmlibs.interfaces import (
|
|
55
|
+
RequirerCommonModel,
|
|
56
|
+
RequirerDataContractV1,
|
|
57
|
+
ResourceCreatedEvent,
|
|
58
|
+
ResourceEntityCreatedEvent,
|
|
59
|
+
ResourceProviderModel,
|
|
60
|
+
ResourceRequirerEventHandler,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
class ClientCharm(CharmBase):
|
|
64
|
+
# Database charm that accepts connections from application charms.
|
|
65
|
+
def __init__(self, *args) -> None:
|
|
66
|
+
super().__init__(*args)
|
|
67
|
+
|
|
68
|
+
requests = [
|
|
69
|
+
RequirerCommonModel(
|
|
70
|
+
resource="clientdb",
|
|
71
|
+
),
|
|
72
|
+
RequirerCommonModel(
|
|
73
|
+
resource="clientbis",
|
|
74
|
+
),
|
|
75
|
+
RequirerCommonModel(
|
|
76
|
+
entity_type="USER",
|
|
77
|
+
)
|
|
78
|
+
]
|
|
79
|
+
self.database = ResourceRequirerEventHandler(
|
|
80
|
+
self,"database", requests, response_model=ResourceProviderModel
|
|
81
|
+
)
|
|
82
|
+
self.framework.observe(self.database.on.resource_created, self._on_resource_created)
|
|
83
|
+
self.framework.observe(self.database.on.resource_entity_created, self._on_entity_created)
|
|
84
|
+
|
|
85
|
+
def _on_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
86
|
+
# Event triggered when a new database is created.
|
|
87
|
+
relation_id = event.relation.id
|
|
88
|
+
response = event.response # This is the response model
|
|
89
|
+
|
|
90
|
+
username = event.response.username
|
|
91
|
+
password = event.response.password
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def _on_entity_created(self, event: ResourceCreatedEvent) -> None:
|
|
95
|
+
# Event triggered when a new entity is created.
|
|
96
|
+
...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Compared to V0, this library makes heavy use of pydantic models, and allows for
|
|
100
|
+
multiple requests, specified as a list.
|
|
101
|
+
On the Requirer side, each response will trigger one custom event for that response.
|
|
102
|
+
This way, it allows for more strategic events to be emitted according to the request.
|
|
103
|
+
|
|
104
|
+
As show above, the library provides some custom events to handle specific situations,
|
|
105
|
+
which are listed below:
|
|
106
|
+
|
|
107
|
+
- resource_created: event emitted when the requested database is created.
|
|
108
|
+
- resource_entity_created: event emitted when the requested entity is created.
|
|
109
|
+
- endpoints_changed: event emitted when the read/write endpoints of
|
|
110
|
+
the database have changed.
|
|
111
|
+
- read_only_endpoints_changed: event emitted when the read-only endpoints of
|
|
112
|
+
the database have changed. Event is not triggered if read/write endpoints
|
|
113
|
+
changed too.
|
|
114
|
+
|
|
115
|
+
If it is needed to connect multiple database clusters to the same relation endpoint
|
|
116
|
+
the application charm can implement the same code as if it would connect to only
|
|
117
|
+
one database cluster (like the above code example).
|
|
118
|
+
|
|
119
|
+
To differentiate multiple clusters connected to the same relation endpoint
|
|
120
|
+
the application charm can use the name of the remote application:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
def _on_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
124
|
+
# Get the remote app name of the cluster that triggered this event
|
|
125
|
+
cluster = event.relation.app.name
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
It is also possible to provide an alias for each different database cluster/relation.
|
|
129
|
+
|
|
130
|
+
So, it is possible to differentiate the clusters in two ways.
|
|
131
|
+
|
|
132
|
+
The first is to use the remote application name, with `event.relation.app.name`.
|
|
133
|
+
|
|
134
|
+
The second way is to use different event handlers to handle each cluster events.
|
|
135
|
+
|
|
136
|
+
The implementation would be something like the following code:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from dpcharmlibs.interfaces import (
|
|
140
|
+
RequirerCommonModel,
|
|
141
|
+
RequirerDataContractV1,
|
|
142
|
+
ResourceCreatedEvent,
|
|
143
|
+
ResourceEntityCreatedEvent,
|
|
144
|
+
ResourceProviderModel,
|
|
145
|
+
ResourceRequirerEventHandler,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
class ApplicationCharm(CharmBase):
|
|
149
|
+
# Application charm that connects to database charms.
|
|
150
|
+
|
|
151
|
+
def __init__(self, *args):
|
|
152
|
+
super().__init__(*args)
|
|
153
|
+
|
|
154
|
+
requests = [
|
|
155
|
+
RequirerCommonModel(
|
|
156
|
+
resource="clientdb",
|
|
157
|
+
),
|
|
158
|
+
RequirerCommonModel(
|
|
159
|
+
resource="clientbis",
|
|
160
|
+
),
|
|
161
|
+
]
|
|
162
|
+
# Define the cluster aliases and one handler for each cluster database
|
|
163
|
+
# created event.
|
|
164
|
+
self.database = ResourceRequirerEventHandler(
|
|
165
|
+
self,
|
|
166
|
+
relation_name="database"
|
|
167
|
+
relations_aliases = ["cluster1", "cluster2"],
|
|
168
|
+
requests=
|
|
169
|
+
)
|
|
170
|
+
self.framework.observe(
|
|
171
|
+
self.database.on.cluster1_resource_created, self._on_cluster1_resource_created
|
|
172
|
+
)
|
|
173
|
+
self.framework.observe(
|
|
174
|
+
self.database.on.cluster2_resource_created, self._on_cluster2_resource_created
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def _on_cluster1_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
178
|
+
# Handle the created database on the cluster named cluster1
|
|
179
|
+
|
|
180
|
+
# Create configuration file for app
|
|
181
|
+
config_file = self._render_app_config_file(
|
|
182
|
+
event.response.username,
|
|
183
|
+
event.response.password,
|
|
184
|
+
event.response.endpoints,
|
|
185
|
+
)
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
def _on_cluster2_resource_created(self, event: ResourceCreatedEvent) -> None:
|
|
189
|
+
# Handle the created database on the cluster named cluster2
|
|
190
|
+
|
|
191
|
+
# Create configuration file for app
|
|
192
|
+
config_file = self._render_app_config_file(
|
|
193
|
+
event.response.username,
|
|
194
|
+
event.response.password,
|
|
195
|
+
event.response.endpoints,
|
|
196
|
+
)
|
|
197
|
+
...
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Provider Charm
|
|
201
|
+
|
|
202
|
+
Following an example of using the ResourceRequestedEvent, in the context of the
|
|
203
|
+
database charm code:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from dpcharmlibs.interfaces import (
|
|
207
|
+
ResourceProviderEventHandler,
|
|
208
|
+
ResourceProviderModel,
|
|
209
|
+
ResourceRequestedEvent,
|
|
210
|
+
RequirerCommonModel,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
class SampleCharm(CharmBase):
|
|
214
|
+
|
|
215
|
+
def __init__(self, *args):
|
|
216
|
+
super().__init__(*args)
|
|
217
|
+
# Charm events defined in the database provides charm library.
|
|
218
|
+
self.provided_database = ResourceProviderEventHandler(
|
|
219
|
+
self, "database", RequirerCommonModel,
|
|
220
|
+
)
|
|
221
|
+
self.framework.observe(self.provided_database.on.resource_requested,
|
|
222
|
+
self._on_resource_requested)
|
|
223
|
+
# Database generic helper
|
|
224
|
+
self.database = DatabaseHelper()
|
|
225
|
+
|
|
226
|
+
def _on_resource_requested(self, event: ResourceRequestedEvent) -> None:
|
|
227
|
+
# Handle the event triggered by a new database requested in the relation
|
|
228
|
+
# Retrieve the database name using the charm library.
|
|
229
|
+
db_name = event.request.resource
|
|
230
|
+
# generate a new user credential
|
|
231
|
+
username = self.database.generate_user(event.request.request_id)
|
|
232
|
+
password = self.database.generate_password(event.request.request_id)
|
|
233
|
+
# set the credentials for the relation
|
|
234
|
+
response = ResourceProviderModel(
|
|
235
|
+
salt=event.request.salt,
|
|
236
|
+
request_id=event.request.request_id,
|
|
237
|
+
resource=db_name,
|
|
238
|
+
username=username,
|
|
239
|
+
password=password,
|
|
240
|
+
...
|
|
241
|
+
)
|
|
242
|
+
self.provided_database.set_response(event.relation.id, response)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
As shown above, the library provides a custom event (resource_requested) to handle
|
|
246
|
+
the situation when an application charm requests a new database to be created.
|
|
247
|
+
It's preferred to subscribe to this event instead of relation changed event to avoid
|
|
248
|
+
creating a new database when other information other than a database name is
|
|
249
|
+
exchanged in the relation databag.
|