charmlibs-interfaces-k8s-backup-target 0.1.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.
- charmlibs_interfaces_k8s_backup_target-0.1.0/.gitignore +184 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/CHANGELOG.md +3 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/PKG-INFO +28 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/README.md +11 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/interface/v0/README.md +60 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/interface/v0/interface.yaml +16 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/interface/v0/schema.py +1 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/pyproject.toml +76 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/ruff.toml +18 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/src/charmlibs/interfaces/k8s_backup_target/__init__.py +89 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/src/charmlibs/interfaces/k8s_backup_target/_backup_target.py +162 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/src/charmlibs/interfaces/k8s_backup_target/_schema.py +130 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/src/charmlibs/interfaces/k8s_backup_target/_version.py +15 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/src/charmlibs/interfaces/k8s_backup_target/py.typed +0 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/tests/unit/test_backup_target.py +266 -0
- charmlibs_interfaces_k8s_backup_target-0.1.0/uv.lock +464 -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
|
+
|
|
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
|
+
# uv.lock from example libraries as we don't commit these
|
|
182
|
+
.example/**/uv.lock
|
|
183
|
+
.tutorial/**/uv.lock
|
|
184
|
+
interfaces/.example/**/uv.lock
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: charmlibs-interfaces-k8s-backup-target
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The charmlibs.interfaces.k8s-backup-target package.
|
|
5
|
+
Project-URL: Repository, https://github.com/canonical/charmlibs
|
|
6
|
+
Project-URL: Issues, https://github.com/canonical/charmlibs/issues
|
|
7
|
+
Author: The Charm Tech team at Canonical
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
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
|
|
15
|
+
Requires-Dist: pydantic
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# charmlibs.interfaces.k8s_backup_target
|
|
19
|
+
|
|
20
|
+
The `k8s_backup_target` interface library.
|
|
21
|
+
|
|
22
|
+
To install, add `charmlibs-interfaces-k8s-backup-target` to your Python dependencies. Then in your Python code, import as:
|
|
23
|
+
|
|
24
|
+
```py
|
|
25
|
+
from charmlibs.interfaces import k8s_backup_target
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
See the [reference documentation](https://documentation.ubuntu.com/charmlibs/reference/charmlibs/interfaces/k8s_backup_target).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# charmlibs.interfaces.k8s_backup_target
|
|
2
|
+
|
|
3
|
+
The `k8s_backup_target` interface library.
|
|
4
|
+
|
|
5
|
+
To install, add `charmlibs-interfaces-k8s-backup-target` to your Python dependencies. Then in your Python code, import as:
|
|
6
|
+
|
|
7
|
+
```py
|
|
8
|
+
from charmlibs.interfaces import k8s_backup_target
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See the [reference documentation](https://documentation.ubuntu.com/charmlibs/reference/charmlibs/interfaces/k8s_backup_target).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# `k8s_backup_target/v0`
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `k8s_backup_target` interface enables a **client charm** to specify what Kubernetes data should be backed up by relating to a **backup charm or backup integrator charm**. In practice, a client charm declares the namespaces and resource types to include or exclude in backups, and the backup charm or integrator uses this specification to forward backup requests to a backup operator. This design lets the client define backup requirements **without direct cluster elevated permissions**, delegating the backup execution to the backup operator charm.
|
|
6
|
+
|
|
7
|
+
## Direction
|
|
8
|
+
|
|
9
|
+
This is a unidirectional interface where the provider (client) sends data and the requirer (backup charm or backup integrator) only receives data. There is no data sent back from the requirer side.
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TD
|
|
13
|
+
Provider -- endpoint, app_name, model, spec --> Requirer
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
- **Provider (Client Charm)**: Provides the backup specification via its application relation data. Typically, a client charm will set this data as soon as the relation is formed.
|
|
19
|
+
- **Requirer (Backup Charm or Backup Integrator Charm)**: Consumes the spec. It does not send any data over the relation. If the requirer is an integrator charm, it should listens for changes to the relation data and forward the received spec to the backup operator it is related to.
|
|
20
|
+
|
|
21
|
+
## Relation Data
|
|
22
|
+
|
|
23
|
+
[\[Pydantic Schema\]](./schema.py)
|
|
24
|
+
|
|
25
|
+
On the provider side, the application data bag must contain structure with the following fields:
|
|
26
|
+
|
|
27
|
+
- `app` (string, required): Name of the client application that requires backups.
|
|
28
|
+
- `relation_name` (string, required): Name of the relation on the client side through which this spec is sent (from metadata.yaml).
|
|
29
|
+
- `model` (string, required): Name of the model where the client application is deployed.
|
|
30
|
+
- `spec` (dict, required): A dictionary defining what to back up. The spec structure is based on [Velero's resource filtering model](https://velero.io/docs/main/resource-filtering/) but is designed to be generic enough for alternative backup providers in future. This includes the following keys:
|
|
31
|
+
- `include_namespaces` (list of str, optional): Specific Kubernetes namespaces to include in the backup. If not provided (None), all namespaces are included.
|
|
32
|
+
- `include_resources` (list of str, optional): Specific Kubernetes resource kinds to include in the backup. If not provided (None), all resource types are included.
|
|
33
|
+
- `exclude_namespaces` (list of str, optional): Namespaces to exclude from the backup.
|
|
34
|
+
- `exclude_resources` (list of str, optional): Resource kinds to exclude from the backup.
|
|
35
|
+
- `include_cluster_resources` (bool, optional): Whether to include cluster-scoped resources in the backup.
|
|
36
|
+
- `label_selector` (dict, optional): A label selector to filter resources for backup.
|
|
37
|
+
- `ttl` (string, optional): Time-to-live duration for the backup (e.g., "72h", "30d").
|
|
38
|
+
|
|
39
|
+
### Provider
|
|
40
|
+
|
|
41
|
+
#### Example
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
application-data:
|
|
45
|
+
app: my-app
|
|
46
|
+
relation_name: backup
|
|
47
|
+
model: my-model
|
|
48
|
+
spec:
|
|
49
|
+
include_namespaces: ["my-namespace"]
|
|
50
|
+
include_resources: ["persistentvolumeclaims", "services", "deployments"]
|
|
51
|
+
exclude_resources: null
|
|
52
|
+
exclude_namespaces: null
|
|
53
|
+
label_selector: {"app": "my-app"}
|
|
54
|
+
include_cluster_resources: false
|
|
55
|
+
ttl: "24h"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Requirer
|
|
59
|
+
|
|
60
|
+
N/A
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: k8s_backup_target
|
|
2
|
+
version: 0
|
|
3
|
+
status: draft
|
|
4
|
+
lib: charmlibs.interfaces.k8s_backup_target
|
|
5
|
+
summary: Configure Kubernetes backup target specifications.
|
|
6
|
+
description: |
|
|
7
|
+
The `k8s_backup_target` interface allows client charms to specify Kubernetes backup requirements to a backup integrator charm.
|
|
8
|
+
The provider charm (client) provides backup specifications including namespaces, resource types, and retention policies, while the requirer charm (backup integrator) receives these specifications and forwards them to a backup operator.
|
|
9
|
+
|
|
10
|
+
requirers:
|
|
11
|
+
- name: velero-integrator-operator
|
|
12
|
+
url: https://github.com/canonical/velero-integrator-operator
|
|
13
|
+
|
|
14
|
+
providers: []
|
|
15
|
+
|
|
16
|
+
maintainer: analytics
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../../src/charmlibs/interfaces/k8s_backup_target/_schema.py
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "charmlibs-interfaces-k8s-backup-target"
|
|
3
|
+
description = "The charmlibs.interfaces.k8s-backup-target package."
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
authors = [
|
|
7
|
+
{name="The Charm Tech team at Canonical"},
|
|
8
|
+
]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"License :: OSI Approved :: Apache Software License",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Operating System :: POSIX :: Linux",
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
]
|
|
16
|
+
dynamic = ["version"]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"ops",
|
|
19
|
+
"pydantic",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
lint = [
|
|
24
|
+
]
|
|
25
|
+
unit = [
|
|
26
|
+
"ops[testing]",
|
|
27
|
+
"pytest",
|
|
28
|
+
]
|
|
29
|
+
functional = [
|
|
30
|
+
]
|
|
31
|
+
integration = [
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
"Repository" = "https://github.com/canonical/charmlibs"
|
|
36
|
+
"Issues" = "https://github.com/canonical/charmlibs/issues"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["hatchling"]
|
|
40
|
+
build-backend = "hatchling.build"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/charmlibs"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.version]
|
|
46
|
+
path = "src/charmlibs/interfaces/k8s_backup_target/_version.py"
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
extend = "../../pyproject.toml"
|
|
50
|
+
src = ["src", "tests/unit", "tests/functional", "tests/integration"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff.format]
|
|
53
|
+
quote-style = "preserve"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
56
|
+
"./**/schema.py" = [
|
|
57
|
+
"CPY", # copyright
|
|
58
|
+
"D", # docs
|
|
59
|
+
"E501", # line too long
|
|
60
|
+
]
|
|
61
|
+
"tests/**/*" = [
|
|
62
|
+
"E501",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.pyright]
|
|
66
|
+
extends = "../../pyproject.toml"
|
|
67
|
+
include = ["src", "tests"]
|
|
68
|
+
pythonVersion = "3.10"
|
|
69
|
+
|
|
70
|
+
[tool.charmlibs.functional]
|
|
71
|
+
ubuntu = []
|
|
72
|
+
pebble = []
|
|
73
|
+
sudo = false
|
|
74
|
+
|
|
75
|
+
[tool.charmlibs.integration]
|
|
76
|
+
tags = []
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
extend = "../../pyproject.toml"
|
|
2
|
+
|
|
3
|
+
[format]
|
|
4
|
+
quote-style = "preserve"
|
|
5
|
+
|
|
6
|
+
[lint.extend-per-file-ignores]
|
|
7
|
+
"./**/schema.py" = [
|
|
8
|
+
"CPY", # copyright
|
|
9
|
+
"D", # docs
|
|
10
|
+
"E501", # line too long
|
|
11
|
+
]
|
|
12
|
+
"./interface/v*/tests/*.py" = [
|
|
13
|
+
"CPY", # copyright
|
|
14
|
+
"D", # docs
|
|
15
|
+
"S", # security
|
|
16
|
+
"E501", # line too long
|
|
17
|
+
"F841", # assignment to unused variable
|
|
18
|
+
]
|
charmlibs_interfaces_k8s_backup_target-0.1.0/src/charmlibs/interfaces/k8s_backup_target/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Copyright 2026 Canonical Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""K8s Backup Target library.
|
|
16
|
+
|
|
17
|
+
This library implements the Requirer and Provider roles for the ``k8s_backup_target`` relation
|
|
18
|
+
interface. It is used by client charms to declare backup specifications, and by backup charms or
|
|
19
|
+
backup integrator charms to consume them and forward to backup operators.
|
|
20
|
+
|
|
21
|
+
The ``k8s_backup_target`` interface allows a charm (the provider) to provide a declarative
|
|
22
|
+
description of what Kubernetes resources should be included in a backup. These specifications are
|
|
23
|
+
sent to the backup charm or backup integrator charm (the requirer), which merges them with schedule
|
|
24
|
+
configuration and forwards to the backup operator.
|
|
25
|
+
|
|
26
|
+
This interface follows a least-privilege model: client charms do not manipulate cluster resources
|
|
27
|
+
themselves. Instead, they define what should be backed up and leave execution to the backup
|
|
28
|
+
operator.
|
|
29
|
+
|
|
30
|
+
Provider Example
|
|
31
|
+
================
|
|
32
|
+
|
|
33
|
+
::
|
|
34
|
+
|
|
35
|
+
from charmlibs.interfaces.k8s_backup_target import (
|
|
36
|
+
K8sBackupTargetProvider,
|
|
37
|
+
K8sBackupTargetSpec,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
class SomeCharm(CharmBase):
|
|
41
|
+
def __init__(self, *args):
|
|
42
|
+
# ...
|
|
43
|
+
self.backup = K8sBackupTargetProvider(
|
|
44
|
+
self,
|
|
45
|
+
relation_name="backup",
|
|
46
|
+
spec=K8sBackupTargetSpec(
|
|
47
|
+
include_namespaces=["my-namespace"],
|
|
48
|
+
include_resources=["persistentvolumeclaims", "services", "deployments"],
|
|
49
|
+
ttl=str(self.config["ttl"]),
|
|
50
|
+
),
|
|
51
|
+
# Optional: refresh the data on custom events
|
|
52
|
+
refresh_event=[self.on.config_changed],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
Requirer Example
|
|
56
|
+
================
|
|
57
|
+
|
|
58
|
+
::
|
|
59
|
+
|
|
60
|
+
from charmlibs.interfaces.k8s_backup_target import (
|
|
61
|
+
K8sBackupTargetRequirer,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
class BackupIntegratorCharm(CharmBase):
|
|
65
|
+
def __init__(self, *args):
|
|
66
|
+
# ...
|
|
67
|
+
self.backup_requirer = K8sBackupTargetRequirer(self, relation_name="k8s-backup-target")
|
|
68
|
+
|
|
69
|
+
def _on_backup_action(self, event):
|
|
70
|
+
spec = self.backup_requirer.get_backup_spec(
|
|
71
|
+
app_name=event.params["app"],
|
|
72
|
+
endpoint=event.params["endpoint"],
|
|
73
|
+
model=event.params["model"],
|
|
74
|
+
)
|
|
75
|
+
...
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
from ._backup_target import (
|
|
79
|
+
K8sBackupTargetProvider,
|
|
80
|
+
K8sBackupTargetRequirer,
|
|
81
|
+
)
|
|
82
|
+
from ._schema import K8sBackupTargetSpec
|
|
83
|
+
from ._version import __version__ as __version__
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
"K8sBackupTargetProvider",
|
|
87
|
+
"K8sBackupTargetRequirer",
|
|
88
|
+
"K8sBackupTargetSpec",
|
|
89
|
+
]
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Copyright 2026 Canonical Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""K8s Backup Target library implementation."""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
import ops
|
|
20
|
+
from ops import BoundEvent, EventBase
|
|
21
|
+
from ops.charm import CharmBase
|
|
22
|
+
from ops.framework import Object
|
|
23
|
+
|
|
24
|
+
from ._schema import BackupTargetEntry, K8sBackupTargetSpec, ProviderAppData
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class K8sBackupTargetRequirer:
|
|
30
|
+
"""Requirer class for the backup target configuration relation."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, charm: CharmBase, relation_name: str):
|
|
33
|
+
"""Initialize the requirer.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
charm: The charm instance that requires backup configuration.
|
|
37
|
+
relation_name: The name of the relation (from metadata.yaml).
|
|
38
|
+
"""
|
|
39
|
+
self._charm = charm
|
|
40
|
+
self._relation_name = relation_name
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _load_provider_data(relation: ops.Relation) -> ProviderAppData | None:
|
|
44
|
+
"""Load and validate provider app data from a relation."""
|
|
45
|
+
try:
|
|
46
|
+
return relation.load(ProviderAppData, relation.app)
|
|
47
|
+
except (ops.RelationDataError, ValueError):
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def is_ready(self) -> bool:
|
|
52
|
+
"""Check if the relation has valid backup target data.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if at least one relation has parseable backup_targets data.
|
|
56
|
+
"""
|
|
57
|
+
relations = self._charm.model.relations[self._relation_name]
|
|
58
|
+
for relation in relations:
|
|
59
|
+
data = self._load_provider_data(relation)
|
|
60
|
+
if data and data.backup_targets:
|
|
61
|
+
return True
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def get_backup_spec(
|
|
65
|
+
self, app_name: str, endpoint: str, model: str
|
|
66
|
+
) -> K8sBackupTargetSpec | None:
|
|
67
|
+
"""Get a K8sBackupTargetSpec for a given (app, endpoint, model).
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
app_name: The name of the application for which the backup is configured.
|
|
71
|
+
endpoint: The name of the relation (from metadata.yaml).
|
|
72
|
+
model: The model name of the application.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The backup specification if available, otherwise None.
|
|
76
|
+
"""
|
|
77
|
+
relations = self._charm.model.relations[self._relation_name]
|
|
78
|
+
|
|
79
|
+
for relation in relations:
|
|
80
|
+
data = self._load_provider_data(relation)
|
|
81
|
+
if not data:
|
|
82
|
+
continue
|
|
83
|
+
for entry in data.backup_targets:
|
|
84
|
+
if (
|
|
85
|
+
entry.app == app_name
|
|
86
|
+
and entry.model == model
|
|
87
|
+
and entry.relation_name == endpoint
|
|
88
|
+
):
|
|
89
|
+
return entry.spec
|
|
90
|
+
|
|
91
|
+
logger.warning("No backup spec found for app '%s' and endpoint '%s'", app_name, endpoint)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class K8sBackupTargetProvider(Object):
|
|
96
|
+
"""Provider class for the backup target configuration relation."""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
charm: CharmBase,
|
|
101
|
+
relation_name: str,
|
|
102
|
+
spec: K8sBackupTargetSpec,
|
|
103
|
+
refresh_event: BoundEvent | list[BoundEvent] | None = None,
|
|
104
|
+
):
|
|
105
|
+
"""Initialize the provider with the specified backup configuration.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
charm: The charm instance that provides backup.
|
|
109
|
+
relation_name: The name of the relation (from metadata.yaml).
|
|
110
|
+
spec: The backup specification to be used.
|
|
111
|
+
refresh_event: Optional event(s) to trigger data sending.
|
|
112
|
+
"""
|
|
113
|
+
super().__init__(charm, relation_name)
|
|
114
|
+
self._charm = charm
|
|
115
|
+
self._app_name = self._charm.app.name
|
|
116
|
+
self._model = self._charm.model.name
|
|
117
|
+
self._relation_name = relation_name
|
|
118
|
+
self._spec = spec
|
|
119
|
+
|
|
120
|
+
self.framework.observe(self._charm.on.leader_elected, self._send_data)
|
|
121
|
+
self.framework.observe(
|
|
122
|
+
self._charm.on[self._relation_name].relation_created, self._send_data
|
|
123
|
+
)
|
|
124
|
+
self.framework.observe(self._charm.on.upgrade_charm, self._send_data)
|
|
125
|
+
|
|
126
|
+
if refresh_event:
|
|
127
|
+
if not isinstance(refresh_event, tuple | list):
|
|
128
|
+
refresh_event = [refresh_event]
|
|
129
|
+
for event in refresh_event:
|
|
130
|
+
self.framework.observe(event, self._send_data)
|
|
131
|
+
|
|
132
|
+
def _send_data(self, event: EventBase):
|
|
133
|
+
"""Handle any event where we should send data to the relation."""
|
|
134
|
+
if not self._charm.model.unit.is_leader():
|
|
135
|
+
logger.debug(
|
|
136
|
+
"K8sBackupTargetProvider handled send_data event when it is not a leader. "
|
|
137
|
+
"Skipping event - no data sent"
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
relations = self._charm.model.relations.get(self._relation_name)
|
|
142
|
+
|
|
143
|
+
if not relations:
|
|
144
|
+
logger.warning(
|
|
145
|
+
"K8sBackupTargetProvider handled send_data event but no relation '%s' found. "
|
|
146
|
+
"Skipping event - no data sent",
|
|
147
|
+
self._relation_name,
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
entry = BackupTargetEntry(
|
|
152
|
+
app=self._app_name,
|
|
153
|
+
relation_name=self._relation_name,
|
|
154
|
+
model=self._model,
|
|
155
|
+
spec=self._spec,
|
|
156
|
+
)
|
|
157
|
+
provider_data = ProviderAppData(
|
|
158
|
+
backup_targets=[entry],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
for relation in relations:
|
|
162
|
+
relation.save(provider_data, self._charm.app)
|