├── CODEOWNERS ├── .jujuignore ├── wheelhouse.txt ├── .gitignore ├── requirements.txt ├── .github ├── workflows │ ├── on_pull_request.yaml │ ├── bot_pr_approval.yaml │ ├── ci.yaml │ └── on_push.yaml ├── .jira_sync_config.yaml └── ISSUE_TEMPLATE │ ├── enhancement_proposal.yml │ └── bug_report.yml ├── renovate.json ├── src ├── any_charm.py ├── any_charm_base.py └── charm.py ├── charmcraft.yaml ├── actions.yaml ├── pyproject.toml ├── tests ├── unit │ └── test_meta.py └── integration │ ├── conftest.py │ ├── test_packages.py │ ├── test_demo.py │ └── test_charm.py ├── tox.ini ├── config.yaml ├── README.md ├── LICENSE └── metadata.yaml /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @canonical/platform-engineering -------------------------------------------------------------------------------- /.jujuignore: -------------------------------------------------------------------------------- 1 | /venv 2 | *.py[cod] 3 | *.charm 4 | /.github 5 | -------------------------------------------------------------------------------- /wheelhouse.txt: -------------------------------------------------------------------------------- 1 | pydantic==2.6.1 2 | pydantic==1.10.14 3 | jsonschema==4.21.1 4 | requests==2.32.3 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | build/ 3 | *.charm 4 | .tox/ 5 | .coverage 6 | __pycache__/ 7 | *.py[cod] 8 | .idea 9 | .vscode -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ops==3.5.0 2 | charmhelpers==1.2.1 3 | setuptools==80.9.0 4 | packaging==25.0 5 | pip==25.3 6 | pyyaml==6.0.3 7 | -------------------------------------------------------------------------------- /.github/workflows/on_pull_request.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | run-tests: 7 | name: Run Tests 8 | uses: ./.github/workflows/ci.yaml 9 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "automerge": true, 4 | "extends": [ 5 | "config:base", 6 | "group:all" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/bot_pr_approval.yaml: -------------------------------------------------------------------------------- 1 | name: Provide approval for bot PRs 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | bot_pr_approval: 8 | uses: canonical/operator-workflows/.github/workflows/bot_pr_approval.yaml@main 9 | secrets: inherit 10 | -------------------------------------------------------------------------------- /.github/.jira_sync_config.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/canonical/gh-jira-sync-bot for config 2 | settings: 3 | jira_project_key: "ISD" 4 | 5 | status_mapping: 6 | opened: Untriaged 7 | closed: done 8 | not_planned: rejected 9 | 10 | add_gh_comment: true 11 | 12 | epic_key: ISD-3981 13 | 14 | label_mapping: 15 | bug: Bug 16 | enhancement: Story 17 | -------------------------------------------------------------------------------- /src/any_charm.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | """This module provides the hook for extending AnyCharm.""" 5 | 6 | from any_charm_base import AnyCharmBase 7 | 8 | 9 | class AnyCharm(AnyCharmBase): 10 | """Passthrough AnyCharmBase by default.""" 11 | 12 | def __init__(self, *args, **kwargs): 13 | super().__init__(*args, **kwargs) 14 | -------------------------------------------------------------------------------- /charmcraft.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | type: charm 5 | platforms: 6 | ubuntu@22.04:amd64: 7 | ubuntu@22.04:arm64: 8 | ubuntu@24.04:amd64: 9 | ubuntu@24.04:arm64: 10 | ubuntu@24.04:s390x: 11 | ubuntu@24.04:ppc64el: 12 | 13 | parts: 14 | charm: {} 15 | wheelhouse: 16 | plugin: nil 17 | source: . 18 | build-packages: 19 | - python3-pip 20 | override-build: | 21 | mkdir -p $CRAFT_PART_INSTALL/wheelhouse 22 | cp $CRAFT_PART_SRC/wheelhouse.txt $CRAFT_PART_INSTALL 23 | for package in $(cat $CRAFT_PART_SRC/wheelhouse.txt) 24 | do 25 | pip wheel --wheel-dir=$CRAFT_PART_INSTALL/wheelhouse --prefer-binary $package 26 | done 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_proposal.yml: -------------------------------------------------------------------------------- 1 | name: Enhancement Proposal 2 | description: File an enhancement proposal 3 | labels: ["Type: Enhancement", "Status: Triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | Thanks for taking the time to fill out this enhancement proposal! Before submitting your issue, please make 9 | sure there isn't already a prior issue concerning this. If there is, please join that discussion instead. 10 | - type: textarea 11 | id: enhancement-proposal 12 | attributes: 13 | label: Enhancement Proposal 14 | description: > 15 | Describe the enhancement you would like to see in as much detail as needed. 16 | validations: 17 | required: true 18 | -------------------------------------------------------------------------------- /actions.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | get-relation-data: 5 | description: Get relation data that are currently attached to this charm. 6 | rpc: 7 | description: > 8 | Invoke any method of the AnyCharm that the return value and arguments can be encoded in JSON. The AnyCharm can be 9 | extended with any method using the src-overwrite config. 10 | params: 11 | method: 12 | type: string 13 | args: 14 | description: A json encoded args list that will be used in the method invocation. 15 | type: string 16 | default: "[]" 17 | kwargs: 18 | description: A json encoded kwargs map that will be used in the method invocation. 19 | type: string 20 | default: "{}" -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | # Testing tools configuration 5 | [tool.coverage.run] 6 | branch = true 7 | 8 | [tool.coverage.report] 9 | show_missing = true 10 | 11 | [tool.pytest.ini_options] 12 | minversion = "6.0" 13 | log_cli_level = "INFO" 14 | 15 | # Formatting tools configuration 16 | [tool.black] 17 | line-length = 99 18 | target-version = ["py38"] 19 | 20 | [tool.isort] 21 | line_length = 99 22 | profile = "black" 23 | 24 | # Linting tools configuration 25 | [tool.flake8] 26 | max-line-length = 99 27 | max-doc-length = 99 28 | max-complexity = 10 29 | exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"] 30 | select = ["E", "W", "F", "C", "N", "R", "D", "H"] 31 | # Ignore W503, E501 because using black creates errors with this 32 | # Ignore D107 Missing docstring in __init__ 33 | ignore = ["W503", "E501", "D107"] 34 | # D100, D101, D102, D103: Ignore missing docstrings in tests 35 | per-file-ignores = ["tests/*:D100,D101,D102,D103,D104"] 36 | docstring-convention = "google" 37 | # Check for properly formatted copyright header in each file 38 | copyright-check = "True" 39 | copyright-author = "Canonical Ltd." 40 | copyright-regexp = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+%(author)s" 41 | -------------------------------------------------------------------------------- /tests/unit/test_meta.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | import importlib.util 5 | import pathlib 6 | import secrets 7 | import shutil 8 | import sys 9 | import tempfile 10 | 11 | 12 | def import_module(path: pathlib.Path): 13 | name = f"dynamic_{secrets.token_hex(8)}" 14 | spec = importlib.util.spec_from_file_location(name, path) 15 | module = importlib.util.module_from_spec(spec) 16 | sys.modules[name] = module 17 | spec.loader.exec_module(module) 18 | return module 19 | 20 | 21 | def test_preserve_original(): 22 | charm_dir = pathlib.Path(__file__).parent.parent.parent 23 | src_dir = charm_dir / "src" 24 | with tempfile.TemporaryDirectory() as tmp: 25 | tmp_dir = pathlib.Path(tmp) 26 | tmp_src = tmp_dir / "src" 27 | tmp_src.mkdir(exist_ok=True) 28 | tmp_src_charm = tmp_src / "charm.py" 29 | shutil.copy(charm_dir / "wheelhouse.txt", tmp_dir) 30 | 31 | original = {} 32 | for file in src_dir.glob("*.py"): 33 | shutil.copy(file, tmp_src) 34 | if file.name != "charm.py": 35 | original[file.name] = file.read_text() 36 | 37 | charm = import_module(tmp_src_charm) 38 | charm.preserve_original() 39 | charm = import_module(tmp_src_charm) 40 | assert charm.original == original 41 | 42 | charm.preserve_original() 43 | charm = import_module(tmp_src_charm) 44 | assert charm.original == original 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["Type: Bug", "Status: Triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | Thanks for taking the time to fill out this bug report! Before submitting your issue, please make 9 | sure you are using the latest version of the charm. If not, please switch to this image prior to 10 | posting your report to make sure it's not already solved. 11 | - type: textarea 12 | id: bug-description 13 | attributes: 14 | label: Bug Description 15 | description: > 16 | If applicable, add screenshots to help explain the problem you are facing. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: reproduction 21 | attributes: 22 | label: To Reproduce 23 | description: > 24 | Please provide a step-by-step instruction of how to reproduce the behavior. 25 | placeholder: | 26 | 1. `juju deploy ...` 27 | 2. `juju relate ...` 28 | 3. `juju status --relations` 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: environment 33 | attributes: 34 | label: Environment 35 | description: > 36 | We need to know a bit more about the context in which you run the charm. 37 | - Are you running Juju locally, on lxd, in multipass or on some other platform? 38 | - What track and channel you deployed the charm from (ie. `latest/edge` or similar). 39 | - Version of any applicable components, like the juju snap, the model controller, lxd, microk8s, and/or multipass. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: logs 44 | attributes: 45 | label: Relevant log output 46 | description: > 47 | Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 48 | Fetch the logs using `juju debug-log --replay` and `kubectl logs ...`. Additional details available in the juju docs 49 | at https://juju.is/docs/olm/juju-logs 50 | render: shell 51 | validations: 52 | required: true 53 | - type: textarea 54 | id: additional-context 55 | attributes: 56 | label: Additional context 57 | 58 | -------------------------------------------------------------------------------- /tests/integration/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | import json 5 | import platform 6 | import subprocess 7 | 8 | import pytest 9 | import pytest_asyncio 10 | from pytest_operator.plugin import OpsTest 11 | 12 | 13 | @pytest_asyncio.fixture 14 | def run_action(ops_test: OpsTest): 15 | async def _run_action(application_name, action_name, **params): 16 | app = ops_test.model.applications[application_name] 17 | action = await app.units[0].run_action(action_name, **params) 18 | await action.wait() 19 | return action.results 20 | 21 | return _run_action 22 | 23 | 24 | @pytest_asyncio.fixture 25 | def run_rpc(run_action): 26 | async def _run_rpc(application_name, action_name, **params): 27 | result = await run_action(application_name, action_name, **params) 28 | return json.loads(result["return"]) 29 | 30 | return _run_rpc 31 | 32 | 33 | @pytest.fixture(name="codename", scope="module") 34 | def codename_fixture(): 35 | """Series codename for deploying any-charm.""" 36 | return subprocess.check_output(["lsb_release", "-cs"]).strip().decode("utf-8") 37 | 38 | 39 | @pytest.fixture(name="series", scope="module") 40 | def series_fixture(): 41 | """Series version for deploying any-charm.""" 42 | return subprocess.check_output(["lsb_release", "-rs"]).strip().decode("utf-8") 43 | 44 | 45 | @pytest_asyncio.fixture(scope="module") 46 | async def any_charm(ops_test: OpsTest, series: str): 47 | any_charm_path = await ops_test.build_charm(".") 48 | any_charm_build_dir = any_charm_path.parent 49 | any_charm_matching_series = list(any_charm_build_dir.rglob(f"*{series}*.charm")) 50 | assert any_charm_matching_series is not None, f"No build found for series {series}" 51 | return any_charm_matching_series[0] 52 | 53 | 54 | @pytest.fixture(scope="module", name="arch") 55 | def arch_fixture(): 56 | """Get the current machine architecture.""" 57 | arch = platform.uname().processor 58 | if arch in ("aarch64", "arm64"): 59 | return "arm64" 60 | if arch in ("ppc64le", "ppc64el"): 61 | return "ppc64el" 62 | if arch in ("x86_64", "amd64"): 63 | return "amd64" 64 | if arch in ("s390x",): 65 | return "s390x" 66 | raise NotImplementedError(f"Unimplemented arch {arch}") 67 | -------------------------------------------------------------------------------- /tests/integration/test_packages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | import json 5 | import logging 6 | import pathlib 7 | import re 8 | import textwrap 9 | 10 | import pytest 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | @pytest.mark.abort_on_fail 16 | async def test_install_python_dependencies(ops_test, any_charm, run_rpc, arch, codename): 17 | any_charm_script = textwrap.dedent( 18 | """\ 19 | from any_charm_base import AnyCharmBase 20 | 21 | class AnyCharm(AnyCharmBase): 22 | def __init__(self, *args, **kwargs): 23 | super().__init__(*args, **kwargs) 24 | 25 | def pydantic_version(self): 26 | import pydantic 27 | return pydantic.VERSION 28 | 29 | def requests_version(self): 30 | import requests 31 | return requests.__version__ 32 | """ 33 | ) 34 | 35 | name = "test-packages" 36 | await ops_test.model.deploy( 37 | any_charm, 38 | application_name=name, 39 | config={ 40 | "python-packages": "pydantic", 41 | "src-overwrite": json.dumps({"any_charm.py": any_charm_script}), 42 | }, 43 | series=codename, 44 | constraints={"arch": arch}, 45 | ), 46 | await ops_test.model.wait_for_idle(status="active") 47 | 48 | pydantic_version = await run_rpc(name, "rpc", method="pydantic_version") 49 | wheelhouse_txt = (pathlib.Path(__file__).parent.parent.parent / "wheelhouse.txt").read_text() 50 | expected_pydantic_version = re.findall( 51 | "^pydantic==(2.+)$", wheelhouse_txt, flags=re.MULTILINE 52 | )[0] 53 | assert pydantic_version == expected_pydantic_version 54 | _, debug_log, _ = await ops_test.juju("debug-log", "--replay") 55 | assert debug_log.count("installing python packages ['pydantic'] from wheelhouse") == 1 56 | 57 | await ops_test.model.applications[name].set_config({"python-packages": "requests==2.32.2"}) 58 | await ops_test.model.wait_for_idle(status="active") 59 | 60 | # requests_version = await run_rpc(name, "rpc", method="requests_version") 61 | # assert requests_version == "2.32.2" 62 | _, debug_log, _ = await ops_test.juju("debug-log", "--replay") 63 | logger.info(debug_log) 64 | assert debug_log.count("installing python packages ['requests==2.32.2'] from pypi") == 1 65 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | [tox] 5 | skipsdist=True 6 | skip_missing_interpreters = True 7 | envlist = lint, unit 8 | 9 | [vars] 10 | src_path = {toxinidir}/src/ 11 | tst_path = {toxinidir}/tests/ 12 | ;lib_path = {toxinidir}/lib/charms/operator_name_with_underscores 13 | all_path = {[vars]src_path} {[vars]tst_path} 14 | 15 | [testenv] 16 | setenv = 17 | PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} 18 | PYTHONBREAKPOINT=ipdb.set_trace 19 | PY_COLORS=1 20 | passenv = 21 | PYTHONPATH 22 | CHARM_BUILD_DIR 23 | MODEL_SETTINGS 24 | 25 | [testenv:fmt] 26 | description = Apply coding style standards to code 27 | deps = 28 | black 29 | isort 30 | commands = 31 | isort {[vars]all_path} 32 | black {[vars]all_path} 33 | 34 | [testenv:lint] 35 | description = Check code against coding style standards 36 | deps = 37 | black 38 | flake8 39 | flake8-docstrings>=1.6.0 40 | flake8-copyright 41 | flake8-builtins 42 | pyproject-flake8 43 | pep8-naming 44 | isort 45 | codespell 46 | commands = 47 | # uncomment the following line if this charm owns a lib 48 | # codespell {[vars]lib_path} 49 | codespell {toxinidir} --skip {toxinidir}/.git --skip {toxinidir}/.tox \ 50 | --skip {toxinidir}/build --skip {toxinidir}/lib --skip {toxinidir}/venv \ 51 | --skip {toxinidir}/.mypy_cache --skip {toxinidir}/icon.svg 52 | # pflake8 wrapper supports config from pyproject.toml 53 | pflake8 {[vars]all_path} 54 | isort --check-only --diff {[vars]all_path} 55 | black --check --diff {[vars]all_path} 56 | 57 | [testenv:unit] 58 | description = Run unit tests 59 | deps = 60 | pytest 61 | coverage[toml] 62 | -r{toxinidir}/requirements.txt 63 | commands = 64 | coverage run --source={[vars]src_path} \ 65 | -m pytest --ignore={[vars]tst_path}integration -v --tb native -s {posargs} 66 | coverage report 67 | 68 | [testenv:integration] 69 | description = Run integration tests 70 | deps = 71 | juju==3.6.0.0 72 | # 2025/11/06 Pin protobuf due to issues with S390x architecture issues. See: 73 | # https://github.com/protocolbuffers/protobuf/issues/24103 74 | protobuf==6.32.1 75 | pytest 76 | pytest-operator 77 | -r{toxinidir}/requirements.txt 78 | commands = 79 | pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} 80 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | workflow_call: 4 | 5 | jobs: 6 | lint: 7 | name: Lint 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v6 12 | - name: Install dependencies 13 | run: python3 -m pip install tox 14 | - name: Run linters 15 | run: tox -e lint 16 | 17 | unit-test: 18 | name: Unit tests 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v6 23 | - name: Install dependencies 24 | run: python -m pip install tox 25 | - name: Run tests 26 | run: tox -e unit 27 | 28 | integration-test-microk8s: 29 | name: Integration tests (microk8s) ${{ matrix.ubuntu-version }} 30 | runs-on: ubuntu-${{ matrix.ubuntu-version }} 31 | strategy: 32 | matrix: 33 | ubuntu-version: ["22.04", "24.04"] 34 | module: [test_charm, test_demo, test_packages] 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v6 38 | - name: Setup operator environment 39 | uses: charmed-kubernetes/actions-operator@main 40 | with: 41 | juju-channel: 3.6/stable 42 | provider: microk8s 43 | channel: 1.29-strict/stable 44 | - name: Run integration tests 45 | run: tox -e integration -- -k ${{ matrix.module }} 46 | 47 | integration-test-lxd: 48 | name: Integration tests (LXD) ${{ matrix.arch }} Ubuntu ${{ matrix.ubuntu-version }} 49 | runs-on: 50 | [self-hosted, "${{ matrix.arch }}", "${{ matrix.ubuntu-codename }}"] 51 | strategy: 52 | matrix: 53 | arch: [s390x, arm64, ppc64le, amd64] 54 | ubuntu-version: ["22.04", "24.04"] 55 | module: [test_charm, test_packages] 56 | include: 57 | - ubuntu-version: "22.04" 58 | ubuntu-codename: jammy 59 | - ubuntu-version: "24.04" 60 | ubuntu-codename: noble 61 | exclude: 62 | - arch: s390x 63 | ubuntu-version: "22.04" 64 | - arch: ppc64le 65 | ubuntu-version: "22.04" 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v6 69 | - name: Setup operator environment 70 | uses: charmed-kubernetes/actions-operator@main 71 | with: 72 | juju-channel: 3/stable 73 | provider: lxd 74 | - name: Install build dependencies 75 | run: sudo apt-get install -y cargo pkg-config 76 | - name: Run integration tests 77 | run: tox -e integration -- -k ${{ matrix.module }} 78 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | options: 5 | src-overwrite: 6 | description: | 7 | A yaml/json encoded map of filenames and file contents that will write to the any-charm src directory before any charm event handling. 8 | By overwriting the any_charm.py with a Python script that defines a class named AnyCharm inherited from the AnyCharm base, the any-charm behavior can be altered and extended. 9 | The src directory has been added to the Python path, you can directly import any modules inside the src directory. 10 | It's recommended to use this config with rpc action. 11 | The charm.py is protected and can not be overwritten, and it's highly not recommended to overwrite any_charm_base.py. 12 | Here's an example of overwriting the any_charm.py, you can use the rpc charm action to invoke the greeting method: 13 | any_charm.py: | 14 | from any_charm_base import AnyCharmBase 15 | 16 | class AnyCharm(AnyCharmBase): 17 | def __init__(self, *args, **kwargs): 18 | super().__init__(*args, **kwargs) 19 | self.framework.observe(self.on.update_status, self.update_status) 20 | 21 | def update_status(self, event): 22 | print("Hello, World!") 23 | 24 | def greeting(self): 25 | return "Hello" 26 | default: "{}" 27 | type: string 28 | python-packages: 29 | description: >- 30 | newline-separated Python packages list to install in any-charm. 31 | The configuration format is a subset of requirements.txt specification, with each line a dependency compliant with PEP 508. 32 | Some commonly used packages in charms like pydantic are packed with the any-charm. 33 | Which means that if the local cached package version matches the requirement, the required package can be installed directly from the cache. 34 | The full list of pre-packaged Python packages can be found within the any-charm source code, specifically in the wheelhouse.txt file. 35 | For better chances to match versions available in the local cache, don't specify or pin the exact package version. 36 | For example, prefer using `jsonschema` instead of `jsonschema==4.21.0`. 37 | And for pre-packed Python packages with different versions like pydantic v1 and v2, only pin the major version `pydantic~=1.0`. 38 | Packages installed via this configuration has the lowest priority during the Python module resolution process. 39 | default: "" 40 | type: string 41 | -------------------------------------------------------------------------------- /tests/integration/test_demo.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | import asyncio 4 | import json 5 | import logging 6 | import textwrap 7 | 8 | import kubernetes 9 | import requests 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | async def test_ingress(ops_test, any_charm, run_action): 15 | any_app_name = "any-ingress" 16 | ingress_lib_url = "https://github.com/canonical/nginx-ingress-integrator-operator/raw/main/lib/charms/nginx_ingress_integrator/v0/ingress.py" 17 | ingress_lib = requests.get(ingress_lib_url, timeout=10).text 18 | any_charm_src_overwrite = { 19 | "ingress.py": ingress_lib, 20 | "any_charm.py": textwrap.dedent( 21 | """\ 22 | from ingress import IngressRequires 23 | from any_charm_base import AnyCharmBase 24 | class AnyCharm(AnyCharmBase): 25 | def __init__(self, *args, **kwargs): 26 | super().__init__(*args, **kwargs) 27 | self.ingress = IngressRequires( 28 | self, 29 | { 30 | "service-hostname": "any", 31 | "service-name": self.app.name, 32 | "service-port": 80 33 | } 34 | ) 35 | def update_ingress(self, ingress_config): 36 | self.ingress.update_config(ingress_config) 37 | """ 38 | ), 39 | } 40 | await asyncio.gather( 41 | ops_test.model.deploy( 42 | "nginx-ingress-integrator", 43 | application_name="ingress", 44 | channel="latest/stable", 45 | revision=79, 46 | trust=True, 47 | ), 48 | ops_test.model.deploy( 49 | any_charm, 50 | application_name=any_app_name, 51 | series="jammy", 52 | config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, 53 | ), 54 | ) 55 | await ops_test.model.add_relation(f"{any_app_name}:ingress", "ingress:ingress") 56 | await ops_test.model.wait_for_idle(status="active") 57 | 58 | await run_action( 59 | any_app_name, 60 | "rpc", 61 | method="update_ingress", 62 | kwargs=json.dumps({"ingress_config": {"owasp-modsecurity-crs": True}}), 63 | ) 64 | await ops_test.model.wait_for_idle(status="active") 65 | 66 | kubernetes.config.load_kube_config() 67 | kube = kubernetes.client.NetworkingV1Api() 68 | 69 | def get_ingress_annotation(): 70 | return kube.read_namespaced_ingress( 71 | "any-ingress", namespace=ops_test.model.name 72 | ).metadata.annotations 73 | 74 | await ops_test.model.block_until( 75 | lambda: "nginx.ingress.kubernetes.io/enable-modsecurity" in get_ingress_annotation(), 76 | timeout=180, 77 | wait_period=5, 78 | ) 79 | ingress_annotations = get_ingress_annotation() 80 | assert ingress_annotations["nginx.ingress.kubernetes.io/enable-modsecurity"] == "true" 81 | -------------------------------------------------------------------------------- /src/any_charm_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | """The base class for AnyCharm to provide the default functionality of any-charm.""" 5 | 6 | import json 7 | import logging 8 | from typing import Iterator 9 | 10 | import ops 11 | import yaml 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | __all__ = ["AnyCharmBase"] 16 | 17 | 18 | class AnyCharmBase(ops.CharmBase): 19 | """Charm the service.""" 20 | 21 | def __init__(self, *args, **kwargs): 22 | super().__init__(*args, **kwargs) 23 | with open("metadata.yaml", encoding="utf-8") as metadata_fo: 24 | metadata = yaml.safe_load(metadata_fo) 25 | self.__relations = list(metadata["provides"]) + list(metadata["requires"]) 26 | self.framework.observe(self.on.get_relation_data_action, self._get_relation_data_) 27 | self.framework.observe(self.on.rpc_action, self._rpc_) 28 | self.framework.observe(self.on.start, self._on_start_) 29 | 30 | def _on_start_(self, event): 31 | self.unit.status = ops.ActiveStatus() 32 | 33 | def __relation_iter(self) -> Iterator[ops.Relation]: 34 | for relation_name in self.__relations: 35 | for relation in self.model.relations[relation_name]: 36 | yield relation 37 | 38 | def __extrack_relation_unit_data(self, relation: ops.Relation): 39 | data = {} 40 | for unit in relation.units: 41 | data[unit.name] = dict(relation.data[unit]) 42 | for unit_idx in range(self.app.planned_units()): 43 | unit_name = f"{self.app.name}/{unit_idx}" 44 | unit = self.model.get_unit(unit_name) 45 | data[unit_name] = dict(relation.data[unit]) 46 | return data 47 | 48 | def _get_relation_data_(self, event): 49 | try: 50 | relation_data_list = [] 51 | for relation in self.__relation_iter(): 52 | relation_data_list.append( 53 | { 54 | "relation": relation.name, 55 | "other_application_name": relation.app.name, 56 | "application_data": { 57 | self.app.name: dict(relation.data[self.app]), 58 | relation.app.name: dict(relation.data[relation.app]), 59 | }, 60 | "unit_data": self.__extrack_relation_unit_data(relation), 61 | } 62 | ) 63 | event.set_results({"relation-data": json.dumps(relation_data_list)}) 64 | except Exception as exc: 65 | logger.exception("error while handling get-relation-data action") 66 | event.fail(repr(exc)) 67 | 68 | def _rpc_(self, event: ops.ActionEvent): 69 | try: 70 | action_params = event.params 71 | method = action_params["method"] 72 | args = json.loads(action_params["args"]) 73 | kwargs = json.loads(action_params["kwargs"]) 74 | result = getattr(self, method)(*args, **kwargs) 75 | event.set_results({"return": json.dumps(result)}) 76 | except Exception as exc: 77 | logger.exception("error while handling rpc action") 78 | event.fail(repr(exc)) 79 | -------------------------------------------------------------------------------- /.github/workflows/on_push.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | run-tests: 9 | name: Run Tests 10 | uses: ./.github/workflows/ci.yaml 11 | 12 | publish-any-charm: 13 | name: Publish any-charm ${{ matrix.arch }} Ubuntu ${{ matrix.ubuntu-version }} 14 | runs-on: ${{ matrix.runs-on }} 15 | needs: [run-tests] 16 | strategy: 17 | matrix: 18 | include: 19 | - arch: amd64 20 | ubuntu-version: "22.04" 21 | runs-on: ubuntu-22.04 22 | - arch: arm64 23 | ubuntu-version: "22.04" 24 | runs-on: [self-hosted, arm64, jammy] 25 | - arch: amd64 26 | ubuntu-version: "24.04" 27 | runs-on: ubuntu-24.04 28 | - arch: arm64 29 | ubuntu-version: "24.04" 30 | runs-on: [self-hosted, arm64, noble] 31 | - arch: ppc64el 32 | ubuntu-version: "24.04" 33 | runs-on: [self-hosted, ppc64el, noble] 34 | - arch: s390x 35 | ubuntu-version: "24.04" 36 | runs-on: [self-hosted, s390x, noble] 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v6 40 | - uses: canonical/setup-lxd@main 41 | with: 42 | channel: 5.21/stable 43 | - name: Upload Charm to Charmhub 44 | uses: canonical/charming-actions/upload-charm@2.7.0 45 | with: 46 | credentials: ${{ secrets.CHARMHUB_TOKEN }} 47 | github-token: ${{ secrets.GITHUB_TOKEN }} 48 | channel: latest/beta 49 | destructive-mode: false 50 | 51 | publish-any-charm-k8s: 52 | name: Publish any-charm-k8s ${{ matrix.arch }} Ubuntu ${{ matrix.ubuntu-version }} 53 | runs-on: ${{ matrix.runs-on }} 54 | needs: [run-tests] 55 | strategy: 56 | matrix: 57 | include: 58 | - arch: amd64 59 | ubuntu-version: "22.04" 60 | runs-on: ubuntu-22.04 61 | - arch: arm64 62 | ubuntu-version: "22.04" 63 | runs-on: [self-hosted, arm64, jammy] 64 | - arch: amd64 65 | ubuntu-version: "24.04" 66 | runs-on: ubuntu-24.04 67 | - arch: arm64 68 | ubuntu-version: "24.04" 69 | runs-on: [self-hosted, arm64, noble] 70 | steps: 71 | - name: Checkout 72 | uses: actions/checkout@v6 73 | - uses: canonical/setup-lxd@main 74 | with: 75 | channel: 5.21/stable 76 | - name: Update metadata.yaml 77 | run: | 78 | yq eval '.name = "any-charm-k8s"' --inplace metadata.yaml 79 | yq eval '.containers = {"any": {"resource": "any-image"}}' --inplace metadata.yaml 80 | yq eval '.resources = { 81 | "any-image": { 82 | "type": "oci-image", 83 | "description": "Any OCI image", 84 | "upstream-source": "ubuntu:latest" 85 | } 86 | }' --inplace metadata.yaml 87 | - name: Upload Charm to Charmhub 88 | uses: canonical/charming-actions/upload-charm@2.7.0 89 | with: 90 | credentials: ${{ secrets.CHARMHUB_TOKEN }} 91 | github-token: ${{ secrets.GITHUB_TOKEN }} 92 | channel: latest/beta 93 | tag-prefix: any-charm-k8s 94 | destructive-mode: false 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # any-charm 2 | 3 | A charm simulates any kind of relation consumer charm to make it easy to write integration tests for relation provider charms. 4 | 5 | ## Usage 6 | 7 | The `AnyCharm` can also be extended to alter the behavior of the `any-charm` by configuring `src-overwrite`. 8 | `src-overwrite` defines a series of files that write into the `src` directory in `any-charm` on-the-fly. 9 | `any_charm.py` in the src directory provides a hook to extend the `AnyCharm`, and the `rpc` action can invoke any method in the `AnyCharm`. 10 | All combined you can create any charm for your test purpose without boilerplate. 11 | 12 | ```python3 13 | async def test_ingress(ops_test, run_action): 14 | any_app_name = "any-ingress" 15 | ingress_lib_url = "https://github.com/canonical/nginx-ingress-integrator-operator/raw/main/lib/charms/nginx_ingress_integrator/v0/ingress.py" 16 | ingress_lib = requests.get(ingress_lib_url, timeout=10).text 17 | any_charm_src_overwrite = { 18 | "ingress.py": ingress_lib, 19 | "any_charm.py": textwrap.dedent( 20 | """\ 21 | from ingress import IngressRequires 22 | from any_charm_base import AnyCharmBase 23 | class AnyCharm(AnyCharmBase): 24 | def __init__(self, *args, **kwargs): 25 | super().__init__(*args, **kwargs) 26 | self.ingress = IngressRequires( 27 | self, 28 | { 29 | "service-hostname": "any", 30 | "service-name": self.app.name, 31 | "service-port": 80 32 | } 33 | ) 34 | def update_ingress(self, ingress_config): 35 | self.ingress.update_config(ingress_config) 36 | """ 37 | ), 38 | } 39 | await asyncio.gather( 40 | ops_test.model.deploy( 41 | "nginx-ingress-integrator", 42 | application_name="ingress", 43 | channel="latest/stable", 44 | revision=79, 45 | trust=True, 46 | ), 47 | ops_test.model.deploy( 48 | "any-charm", 49 | application_name=any_app_name, 50 | channel="beta", 51 | config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, 52 | ), 53 | ) 54 | await ops_test.model.add_relation(any_app_name, "ingress:ingress") 55 | await ops_test.model.wait_for_idle(status="active") 56 | 57 | await run_action( 58 | any_app_name, 59 | "rpc", 60 | method="update_ingress", 61 | kwargs=json.dumps({"ingress_config": {"owasp-modsecurity-crs": True}}), 62 | ) 63 | await ops_test.model.wait_for_idle(status="active") 64 | 65 | kubernetes.config.load_kube_config() 66 | kube = kubernetes.client.NetworkingV1Api() 67 | 68 | def get_ingress_annotation(): 69 | return kube.read_namespaced_ingress( 70 | "any-ingress", namespace=ops_test.model.name 71 | ).metadata.annotations 72 | 73 | await ops_test.model.block_until( 74 | lambda: "nginx.ingress.kubernetes.io/enable-modsecurity" 75 | in get_ingress_annotation(), 76 | timeout=180, 77 | wait_period=5, 78 | ) 79 | ingress_annotations = get_ingress_annotation() 80 | assert ( 81 | ingress_annotations["nginx.ingress.kubernetes.io/enable-modsecurity"] == "true" 82 | ) 83 | 84 | ``` 85 | -------------------------------------------------------------------------------- /tests/integration/test_charm.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | import asyncio 5 | import json 6 | import logging 7 | import textwrap 8 | 9 | import pytest 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | @pytest.mark.abort_on_fail 15 | async def test_deploy(ops_test, any_charm, arch, codename): 16 | """Build the charm-under-test and deploy it.""" 17 | await asyncio.gather( 18 | ops_test.model.deploy( 19 | any_charm, application_name="this", series=codename, constraints={"arch": arch} 20 | ), 21 | ops_test.model.deploy( 22 | any_charm, application_name="other", series=codename, constraints={"arch": arch} 23 | ), 24 | ) 25 | await ops_test.model.wait_for_idle(status="active") 26 | 27 | 28 | async def test_relation_and_config(ops_test, run_action): 29 | overwrite_app_charm_script = textwrap.dedent( 30 | """\ 31 | from any_charm_base import AnyCharmBase 32 | 33 | class AnyCharm(AnyCharmBase): 34 | def __init__(self, *args, **kwargs): 35 | super().__init__(*args, **kwargs) 36 | self.framework.observe(self.on['require-any'].relation_changed, self._relation_changed) 37 | self.framework.observe(self.on['provide-any'].relation_changed, self._relation_changed) 38 | 39 | def _relation_changed(self, event): 40 | if self.unit.is_leader(): 41 | event.relation.data[self.model.app]['value'] = self.app.name 42 | event.relation.data[self.unit]['value'] = self.unit.name 43 | """ 44 | ) 45 | for app_name in ("this", "other"): 46 | await ops_test.model.applications[app_name].set_config( 47 | { 48 | "src-overwrite": json.dumps( 49 | { 50 | "any_charm.py": overwrite_app_charm_script, 51 | } 52 | ) 53 | } 54 | ) 55 | await ops_test.model.wait_for_idle(status="active") 56 | await ops_test.model.add_relation("this", "other:provide-any") 57 | await ops_test.model.wait_for_idle(status="active") 58 | results = await run_action("other", "get-relation-data") 59 | 60 | assert "relation-data" in results 61 | relation_data = json.loads(results["relation-data"]) 62 | assert relation_data[0]["relation"] == "provide-any" 63 | assert relation_data[0]["application_data"] == { 64 | "other": {"value": "other"}, 65 | "this": {"value": "this"}, 66 | } 67 | assert relation_data[0]["unit_data"]["this/0"]["value"] == "this/0" 68 | assert relation_data[0]["unit_data"]["other/0"]["value"] == "other/0" 69 | 70 | 71 | async def test_overwrite_and_rpc_action(ops_test, run_action): 72 | overwrite_app_charm_script = textwrap.dedent( 73 | """\ 74 | from any_charm_base import AnyCharmBase 75 | from import_test import identity 76 | class AnyCharm(AnyCharmBase): 77 | def echo(self, *args, **kwargs): 78 | return identity({"args": args, "kwargs": kwargs}) 79 | """ 80 | ) 81 | overwrite_import_test_script = "identity = lambda x: x" 82 | await ops_test.model.applications["this"].set_config( 83 | { 84 | "src-overwrite": json.dumps( 85 | { 86 | "any_charm.py": overwrite_app_charm_script, 87 | "import_test.py": overwrite_import_test_script, 88 | } 89 | ) 90 | } 91 | ) 92 | await ops_test.model.wait_for_idle(status="active") 93 | rpc_params = {"args": [1, 2, "3"], "kwargs": {"a": "b"}} 94 | results = await run_action( 95 | "this", 96 | "rpc", 97 | method="echo", 98 | args=json.dumps(rpc_params["args"]), 99 | kwargs=json.dumps(rpc_params["kwargs"]), 100 | ) 101 | assert json.loads(results["return"]) == rpc_params 102 | 103 | 104 | async def test_recovery(ops_test, run_action): 105 | overwrite_app_charm_script = textwrap.dedent( 106 | """\ 107 | import ops 108 | class AnyCharm(ops.CharmBase): 109 | pass 110 | """ 111 | ) 112 | await ops_test.model.applications["this"].set_config( 113 | { 114 | "src-overwrite": json.dumps( 115 | { 116 | "any_charm.py": overwrite_app_charm_script, 117 | "any_charm_base.py": "", 118 | } 119 | ) 120 | } 121 | ) 122 | await ops_test.model.wait_for_idle(status="active") 123 | await ops_test.model.applications["this"].set_config({"src-overwrite": json.dumps({})}) 124 | await ops_test.model.wait_for_idle(status="active") 125 | results = await run_action("this", "get-relation-data") 126 | assert "relation-data" in results 127 | -------------------------------------------------------------------------------- /src/charm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2024 Canonical Ltd. 3 | # See LICENSE file for licensing details. 4 | 5 | """any-charm entrypoint.""" 6 | 7 | import ast 8 | import os 9 | import pathlib 10 | import shutil 11 | import subprocess 12 | import sys 13 | 14 | import yaml 15 | from charmhelpers.core import hookenv 16 | from ops.main import main 17 | from packaging.requirements import Requirement 18 | from packaging.version import Version 19 | 20 | SRC_DIR = pathlib.Path(os.path.abspath(os.path.split(__file__)[0])) 21 | CHARM_DIR = SRC_DIR.parent 22 | THIS_FILE = pathlib.Path(__file__) 23 | WHEELHOUSE_PACKAGES = [ 24 | (line.split("==")[0], Version(line.split("==")[1])) 25 | for line in (CHARM_DIR / "wheelhouse.txt").read_text().splitlines() 26 | ] 27 | WHEELHOUSE_DIR = CHARM_DIR / "wheelhouse" 28 | DYNAMIC_PACKAGES_PATH = CHARM_DIR / "dynamic-packages" 29 | 30 | original = {} 31 | installed_python_packages = "" 32 | 33 | 34 | def self_modify_assign_value(symbol: str, value_repr: str): 35 | """Search the source code for an assign statement and update its right value in the code.""" 36 | source = THIS_FILE.read_text(encoding="utf-8") 37 | root = ast.parse(source) 38 | source_lines = source.splitlines(keepends=True) 39 | for stmt in ast.iter_child_nodes(root): 40 | if not ( 41 | isinstance(stmt, ast.Assign) 42 | and len(stmt.targets) == 1 43 | and isinstance(stmt.targets[0], ast.Name) 44 | and stmt.targets[0].id == symbol 45 | ): 46 | continue 47 | value = stmt.value 48 | start_offset = ( 49 | sum(len(line) for line in source_lines[: value.lineno - 1]) + value.col_offset 50 | ) 51 | end_offset = ( 52 | sum(len(line) for line in source_lines[: value.end_lineno - 1]) + value.end_col_offset 53 | ) 54 | new_source = source[:start_offset] 55 | new_source += value_repr 56 | new_source += source[end_offset:] 57 | THIS_FILE.write_text(new_source, encoding="utf-8") 58 | break 59 | else: 60 | raise RuntimeError(f"failed to modifying {symbol} value in source code") 61 | 62 | 63 | def pip_install(requirements: str): 64 | """Install Python dependencies listed in requirements.""" 65 | install_wheelhouse = [] 66 | install_pypi = [] 67 | for line in requirements.splitlines(): 68 | if not line.strip(): 69 | continue 70 | req = Requirement(line) 71 | if req.url is not None: 72 | install_pypi.append(line) 73 | continue 74 | if req.extras: 75 | install_pypi.append(line) 76 | continue 77 | for name, version in WHEELHOUSE_PACKAGES: 78 | if name == req.name and version in req.specifier: 79 | install_wheelhouse.append(line) 80 | break 81 | else: 82 | install_pypi.append(line) 83 | if install_wheelhouse: 84 | hookenv.log(f"installing python packages {install_wheelhouse} from wheelhouse") 85 | subprocess.check_call( 86 | [ 87 | sys.executable, 88 | "-m", 89 | "pip", 90 | "install", 91 | "--root-user-action=ignore", 92 | f"--target={DYNAMIC_PACKAGES_PATH}", 93 | "--no-index", 94 | f"--find-links={WHEELHOUSE_DIR}", 95 | *install_wheelhouse, 96 | ] 97 | ) 98 | if install_pypi: 99 | hookenv.log(f"installing python packages {install_pypi} from pypi") 100 | subprocess.check_call( 101 | [ 102 | sys.executable, 103 | "-m", 104 | "pip", 105 | "install", 106 | "--root-user-action=ignore", 107 | f"--target={DYNAMIC_PACKAGES_PATH}", 108 | *install_pypi, 109 | ] 110 | ) 111 | 112 | 113 | def preserve_original(): 114 | """Save the original src file contents.""" 115 | global original 116 | if not original: 117 | original = { 118 | str(f.relative_to(SRC_DIR)): f.read_text(encoding="utf-8") 119 | for f in SRC_DIR.iterdir() 120 | if f.name.endswith(".py") and not f.samefile(THIS_FILE) 121 | } 122 | self_modify_assign_value(f"{original=}".split("=")[0], repr(original)) 123 | 124 | 125 | def install_packages(): 126 | """Install required Python packages.""" 127 | python_packages = hookenv.config("python-packages") 128 | if python_packages != installed_python_packages: 129 | shutil.rmtree(DYNAMIC_PACKAGES_PATH, ignore_errors=True) 130 | os.mkdir(DYNAMIC_PACKAGES_PATH) 131 | pip_install(python_packages) 132 | self_modify_assign_value( 133 | f"{installed_python_packages=}".split("=")[0], repr(python_packages) 134 | ) 135 | 136 | 137 | def src_overwrite(): 138 | """Update the src file contents based on the charm configuration.""" 139 | for src_overwrite_filename, src_overwrite_file_content in { 140 | **original, 141 | **yaml.safe_load(hookenv.config("src-overwrite")), 142 | }.items(): 143 | overwrite_path = SRC_DIR / src_overwrite_filename 144 | if overwrite_path.exists() and THIS_FILE.samefile(overwrite_path): 145 | continue 146 | overwrite_path.parent.mkdir(exist_ok=True) 147 | overwrite_path.write_text(src_overwrite_file_content) 148 | 149 | 150 | if __name__ == "__main__": 151 | preserve_original() 152 | install_packages() 153 | src_overwrite() 154 | sys.path.append(str(SRC_DIR)) 155 | sys.path.append(str(DYNAMIC_PACKAGES_PATH)) 156 | from any_charm import AnyCharm 157 | 158 | main(AnyCharm) 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 Canonical Ltd. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | name: any-charm 5 | description: A charm used to test other charms. 6 | summary: A charm used to test other charms. 7 | 8 | peers: 9 | peer-any: 10 | interface: peer-any 11 | provides: 12 | provide-aar: 13 | interface: aar 14 | provide-agent-auth: 15 | interface: agent-auth 16 | provide-airbyte-server: 17 | interface: airbyte-server 18 | provide-alertmanager-dispatch: 19 | interface: alertmanager_dispatch 20 | provide-alertmanager-remote-configuration: 21 | interface: alertmanager_remote_configuration 22 | provide-anbox-stream-gateway: 23 | interface: anbox-stream-gateway 24 | provide-any: 25 | interface: any 26 | provide-apache-vhost-config: 27 | interface: apache-vhost-config 28 | provide-apache-website: 29 | interface: apache-website 30 | provide-arangodb: 31 | interface: arangodb 32 | provide-auth-proxy: 33 | interface: auth_proxy 34 | provide-autoscaling: 35 | interface: autoscaling 36 | provide-aws-iam: 37 | interface: aws-iam 38 | provide-aws-integration: 39 | interface: aws-integration 40 | provide-azure-integration: 41 | interface: azure-integration 42 | provide-barbican-hsm: 43 | interface: barbican-hsm 44 | provide-barbican-secrets: 45 | interface: barbican-secrets 46 | provide-baremetal: 47 | interface: baremetal 48 | provide-bgp: 49 | interface: bgp 50 | provide-bind-rndc: 51 | interface: bind-rndc 52 | provide-block-storage: 53 | interface: block-storage 54 | provide-cassandra: 55 | interface: cassandra 56 | provide-catalogue: 57 | interface: catalogue 58 | provide-ceilometer: 59 | interface: ceilometer 60 | provide-ceph-admin: 61 | interface: ceph-admin 62 | provide-ceph-bootstrap: 63 | interface: ceph-bootstrap 64 | provide-ceph-client: 65 | interface: ceph-client 66 | provide-ceph-dashboard: 67 | interface: ceph-dashboard 68 | provide-ceph-iscsi-admin-access: 69 | interface: ceph-iscsi-admin-access 70 | provide-ceph-mds: 71 | interface: ceph-mds 72 | provide-ceph-osd: 73 | interface: ceph-osd 74 | provide-ceph-radosgw: 75 | interface: ceph-radosgw 76 | provide-ceph-rbd-mirror: 77 | interface: ceph-rbd-mirror 78 | provide-certificate-transfer: 79 | interface: certificate_transfer 80 | provide-cinder: 81 | interface: cinder 82 | provide-cinder-backend: 83 | interface: cinder-backend 84 | provide-cinder-backup: 85 | interface: cinder-backup 86 | provide-cinder-ceph-key: 87 | interface: cinder-ceph-key 88 | provide-cinder-gw: 89 | interface: cinder-gw 90 | provide-cinder-nedge: 91 | interface: cinder-nedge 92 | provide-cloudflared-route: 93 | interface: cloudflared-route 94 | provide-config-server: 95 | interface: config-server 96 | provide-container-runtime: 97 | interface: container-runtime 98 | provide-containerd: 99 | interface: containerd 100 | provide-containers: 101 | interface: containers 102 | provide-cos-agent: 103 | interface: cos_agent 104 | provide-cos-k8s-tokens: 105 | interface: cos-k8s-tokens 106 | provide-dashboard: 107 | interface: dashboard 108 | provide-dashboard-plugin: 109 | interface: dashboard-plugin 110 | provide-db: 111 | interface: db 112 | provide-db2: 113 | interface: db2 114 | provide-designate: 115 | interface: designate 116 | provide-dex-oidc-config: 117 | interface: dex-oidc-config 118 | provide-dns-record: 119 | interface: dns_record 120 | provide-dns-transfer: 121 | interface: dns_transfer 122 | provide-docker-registry: 123 | interface: docker-registry 124 | provide-dockerhost: 125 | interface: dockerhost 126 | provide-elastic-beats: 127 | interface: elastic-beats 128 | provide-elasticsearch: 129 | interface: elasticsearch 130 | provide-elasticsearch-datastore: 131 | interface: elasticsearch-datastore 132 | provide-ephemeral-backend: 133 | interface: ephemeral-backend 134 | provide-etcd: 135 | interface: etcd 136 | provide-etcd-proxy: 137 | interface: etcd-proxy 138 | provide-event-service: 139 | interface: event-service 140 | provide-external-cloud-provider: 141 | interface: external_cloud_provider 142 | provide-external-provider: 143 | interface: external_provider 144 | provide-fiveg-core-gnb: 145 | interface: fiveg_core_gnb 146 | provide-fiveg-n2: 147 | interface: fiveg_n2 148 | provide-fluentbit: 149 | interface: fluentbit 150 | provide-forward-auth: 151 | interface: forward_auth 152 | provide-ftn-compiler: 153 | interface: ftn-compiler 154 | provide-gcp-integration: 155 | interface: gcp-integration 156 | provide-generic-ip-port-user-pass: 157 | interface: generic-ip-port-user-pass 158 | provide-giraph: 159 | interface: giraph 160 | provide-github-runner-image-v0: 161 | interface: github_runner_image_v0 162 | provide-glance: 163 | interface: glance 164 | provide-glance-backend: 165 | interface: glance-backend 166 | provide-glance-simplestreams-sync: 167 | interface: glance-simplestreams-sync 168 | provide-glauth-auxiliary: 169 | interface: glauth_auxiliary 170 | provide-gnocchi: 171 | interface: gnocchi 172 | provide-grafana-auth: 173 | interface: grafana_auth 174 | provide-grafana-cloud-config: 175 | interface: grafana_cloud_config 176 | provide-grafana-dashboard: 177 | interface: grafana_dashboard 178 | provide-grafana-datasource: 179 | interface: grafana_datasource 180 | provide-grafana-datasource-exchange: 181 | interface: grafana_datasource_exchange 182 | provide-grafana-metadata: 183 | interface: grafana_metadata 184 | provide-grafana-source: 185 | interface: grafana-source 186 | provide-guacd: 187 | interface: guacd 188 | provide-hacluster: 189 | interface: hacluster 190 | provide-haproxy-route: 191 | interface: haproxy-route 192 | provide-heat-plugin-subordinate: 193 | interface: heat-plugin-subordinate 194 | provide-http: 195 | interface: http 196 | provide-http-proxy: 197 | interface: http_proxy 198 | provide-httpd: 199 | interface: httpd 200 | provide-hydra-endpoints: 201 | interface: hydra_endpoints 202 | provide-influxdb-api: 203 | interface: influxdb-api 204 | provide-infoblox: 205 | interface: infoblox 206 | provide-ingress: 207 | interface: ingress 208 | provide-ingress-auth: 209 | interface: ingress-auth 210 | provide-ingress-per-unit: 211 | interface: ingress_per_unit 212 | provide-irc-bridge: 213 | interface: irc_bridge 214 | provide-istio-gateway-info: 215 | interface: istio-gateway-info 216 | provide-java: 217 | interface: java 218 | provide-jenkins-agent-v0: 219 | interface: jenkins_agent_v0 220 | provide-jenkins-extension: 221 | interface: jenkins-extension 222 | provide-jenkins-slave: 223 | interface: jenkins-slave 224 | provide-k8s-cluster: 225 | interface: k8s-cluster 226 | provide-k8s-service: 227 | interface: k8s-service 228 | provide-kafka: 229 | interface: kafka 230 | provide-kafka-client: 231 | interface: kafka_client 232 | provide-kapacitor: 233 | interface: kapacitor 234 | provide-karma-dashboard: 235 | interface: karma_dashboard 236 | provide-keystone: 237 | interface: keystone 238 | provide-keystone-admin: 239 | interface: keystone-admin 240 | provide-keystone-credentials: 241 | interface: keystone-credentials 242 | provide-keystone-domain-backend: 243 | interface: keystone-domain-backend 244 | provide-keystone-fid-service-provider: 245 | interface: keystone-fid-service-provider 246 | provide-keystone-middleware: 247 | interface: keystone-middleware 248 | provide-keystone-notifications: 249 | interface: keystone-notifications 250 | provide-kratos-info: 251 | interface: kratos_info 252 | provide-kube-control: 253 | interface: kube-control 254 | provide-kube-dns: 255 | interface: kube-dns 256 | provide-kubeflow-dashboard-links: 257 | interface: kubeflow_dashboard_links 258 | provide-kubernetes-cni: 259 | interface: kubernetes-cni 260 | provide-kubernetes-info: 261 | interface: kubernetes-info 262 | provide-landscape-hosted: 263 | interface: landscape-hosted 264 | provide-ldap: 265 | interface: ldap 266 | provide-livepatch-pro-airgapped-server: 267 | interface: livepatch-pro-airgapped-server 268 | provide-lldp: 269 | interface: lldp 270 | provide-loadbalancer: 271 | interface: loadbalancer 272 | provide-local-monitors: 273 | interface: local-monitors 274 | provide-login-ui-endpoints: 275 | interface: login_ui_endpoints 276 | provide-logs: 277 | interface: logs 278 | provide-logstash-client: 279 | interface: logstash-client 280 | provide-loki-push-api: 281 | interface: loki_push_api 282 | provide-lte-core: 283 | interface: lte-core 284 | provide-lxd: 285 | interface: lxd 286 | provide-lxd-bgp: 287 | interface: lxd-bgp 288 | provide-lxd-dns: 289 | interface: lxd-dns 290 | provide-lxd-https: 291 | interface: lxd-https 292 | provide-lxd-metrics: 293 | interface: lxd-metrics 294 | provide-magma-orchestrator: 295 | interface: magma-orchestrator 296 | provide-manila-plugin: 297 | interface: manila-plugin 298 | provide-matrix-auth: 299 | interface: matrix_auth 300 | provide-memcache: 301 | interface: memcache 302 | provide-midonet: 303 | interface: midonet 304 | provide-mongodb: 305 | interface: mongodb 306 | provide-mongodb-client: 307 | interface: mongodb_client 308 | provide-monitor: 309 | interface: monitor 310 | provide-monitors: 311 | interface: monitors 312 | provide-mount: 313 | interface: mount 314 | provide-munin-node: 315 | interface: munin-node 316 | provide-mysql: 317 | interface: mysql 318 | provide-mysql-async: 319 | interface: mysql_async 320 | provide-mysql-async-replication: 321 | interface: mysql-async-replication 322 | provide-mysql-client: 323 | interface: mysql_client 324 | provide-mysql-monitor: 325 | interface: mysql-monitor 326 | provide-mysql-root: 327 | interface: mysql-root 328 | provide-mysql-router: 329 | interface: mysql-router 330 | provide-mysql-shared: 331 | interface: mysql-shared 332 | provide-nats: 333 | interface: nats 334 | provide-nedge: 335 | interface: nedge 336 | provide-neutron-api: 337 | interface: neutron-api 338 | provide-neutron-load-balancer: 339 | interface: neutron-load-balancer 340 | provide-neutron-plugin: 341 | interface: neutron-plugin 342 | provide-neutron-plugin-api: 343 | interface: neutron-plugin-api 344 | provide-neutron-plugin-api-subordinate: 345 | interface: neutron-plugin-api-subordinate 346 | provide-nginx-route: 347 | interface: nginx-route 348 | provide-nova: 349 | interface: nova 350 | provide-nova-ceilometer: 351 | interface: nova-ceilometer 352 | provide-nova-cell: 353 | interface: nova-cell 354 | provide-nova-compute: 355 | interface: nova-compute 356 | provide-nova-vgpu: 357 | interface: nova-vgpu 358 | provide-nova-vmware: 359 | interface: nova-vmware 360 | provide-nrpe: 361 | interface: nrpe 362 | provide-nrpe-external-master: 363 | interface: nrpe-external-master 364 | provide-ntp: 365 | interface: ntp 366 | provide-oathkeeper-info: 367 | interface: oathkeeper_info 368 | provide-oauth: 369 | interface: oauth 370 | provide-object-storage: 371 | interface: object-storage 372 | provide-odl-controller-api: 373 | interface: odl-controller-api 374 | provide-oidc-client: 375 | interface: oidc-client 376 | provide-openfga: 377 | interface: openfga 378 | provide-opensearch-client: 379 | interface: opensearch_client 380 | provide-openstack-integration: 381 | interface: openstack-integration 382 | provide-openstack-loadbalancer: 383 | interface: openstack-loadbalancer 384 | provide-ovsdb: 385 | interface: ovsdb 386 | provide-ovsdb-cluster: 387 | interface: ovsdb-cluster 388 | provide-ovsdb-cms: 389 | interface: ovsdb-cms 390 | provide-ovsdb-manager: 391 | interface: ovsdb-manager 392 | provide-ovsdb-subordinate: 393 | interface: ovsdb-subordinate 394 | provide-pacemaker-remote: 395 | interface: pacemaker-remote 396 | provide-parca-scrape: 397 | interface: parca_scrape 398 | provide-parca-store: 399 | interface: parca_store 400 | provide-peer-cluster: 401 | interface: peer_cluster 402 | provide-pgsql: 403 | interface: pgsql 404 | provide-placement: 405 | interface: placement 406 | provide-pod-defaults: 407 | interface: pod-defaults 408 | provide-postfix-metrics: 409 | interface: postfix-metrics 410 | provide-postgresql-async: 411 | interface: postgresql_async 412 | provide-postgresql-client: 413 | interface: postgresql_client 414 | provide-prolog-epilog: 415 | interface: prolog-epilog 416 | provide-prometheus: 417 | interface: prometheus 418 | provide-prometheus-manual: 419 | interface: prometheus-manual 420 | provide-prometheus-remote-write: 421 | interface: prometheus_remote_write 422 | provide-prometheus-rules: 423 | interface: prometheus-rules 424 | provide-prometheus-scrape: 425 | interface: prometheus_scrape 426 | provide-public-address: 427 | interface: public-address 428 | provide-quantum: 429 | interface: quantum 430 | provide-rabbitmq: 431 | interface: rabbitmq 432 | provide-radosgw-multisite: 433 | interface: radosgw-multisite 434 | provide-radosgw-user: 435 | interface: radosgw-user 436 | provide-ranger-client: 437 | interface: ranger_client 438 | provide-redis: 439 | interface: redis 440 | provide-register-application: 441 | interface: register-application 442 | provide-rest: 443 | interface: rest 444 | provide-s3: 445 | interface: s3 446 | provide-saml: 447 | interface: saml 448 | provide-script-provider: 449 | interface: script-provider 450 | provide-sdcore-config: 451 | interface: sdcore_config 452 | provide-sdn-plugin: 453 | interface: sdn-plugin 454 | provide-secrets: 455 | interface: secrets 456 | provide-sentry-metrics: 457 | interface: sentry-metrics 458 | provide-service-control: 459 | interface: service-control 460 | provide-service-mesh: 461 | interface: service-mesh 462 | provide-shards: 463 | interface: shards 464 | provide-slurmd: 465 | interface: slurmd 466 | provide-slurmdbd: 467 | interface: slurmdbd 468 | provide-slurmrestd: 469 | interface: slurmrestd 470 | provide-smtp: 471 | interface: smtp 472 | provide-squid-auth-helper: 473 | interface: squid-auth-helper 474 | provide-ssl-termination: 475 | interface: ssl-termination 476 | provide-statistics: 477 | interface: statistics 478 | provide-stun-server: 479 | interface: stun-server 480 | provide-swift: 481 | interface: swift 482 | provide-swift-global-cluster: 483 | interface: swift-global-cluster 484 | provide-swift-gw: 485 | interface: swift-gw 486 | provide-swift-proxy: 487 | interface: swift-proxy 488 | provide-syslog: 489 | interface: syslog 490 | provide-telegraf-exec: 491 | interface: telegraf-exec 492 | provide-temporal: 493 | interface: temporal 494 | provide-thruk-agent: 495 | interface: thruk-agent 496 | provide-tls-certificates: 497 | interface: tls-certificates 498 | provide-tokens: 499 | interface: tokens 500 | provide-tracing: 501 | interface: tracing 502 | provide-traefik-route: 503 | interface: traefik_route 504 | provide-trino-client: 505 | interface: trino_client 506 | provide-ubuntu: 507 | interface: ubuntu 508 | provide-udldap-userdata: 509 | interface: udldap-userdata 510 | provide-untrusted-container-runtime: 511 | interface: untrusted-container-runtime 512 | provide-user-group: 513 | interface: user-group 514 | provide-vault-autounseal: 515 | interface: vault-autounseal 516 | provide-vault-kv: 517 | interface: vault-kv 518 | provide-vsd-rest-api: 519 | interface: vsd-rest-api 520 | provide-vsphere-integration: 521 | interface: vsphere-integration 522 | provide-web-publish: 523 | interface: web-publish 524 | provide-websso-fid-service-provider: 525 | interface: websso-fid-service-provider 526 | provide-websso-trusted-dashboard: 527 | interface: websso-trusted-dashboard 528 | provide-xlc-compiler: 529 | interface: xlc-compiler 530 | provide-zookeeper: 531 | interface: zookeeper 532 | provide-zuul: 533 | interface: zuul 534 | requires: 535 | # for backwards compatibility 536 | ingress: 537 | interface: ingress 538 | redis: 539 | interface: redis 540 | nginx-route: 541 | interface: nginx-route 542 | saml: 543 | interface: saml 544 | smtp: 545 | interface: smtp 546 | smtp-legacy: 547 | interface: smtp 548 | ldap: 549 | interface: ldap 550 | send-ca-cert: 551 | interface: certificate_transfer 552 | dns-record: 553 | interface: dns_record 554 | require-aar: 555 | interface: aar 556 | require-agent-auth: 557 | interface: agent-auth 558 | require-airbyte-server: 559 | interface: airbyte-server 560 | require-alertmanager-dispatch: 561 | interface: alertmanager_dispatch 562 | require-alertmanager-remote-configuration: 563 | interface: alertmanager_remote_configuration 564 | require-anbox-stream-gateway: 565 | interface: anbox-stream-gateway 566 | require-any: 567 | interface: any 568 | require-apache-vhost-config: 569 | interface: apache-vhost-config 570 | require-apache-website: 571 | interface: apache-website 572 | require-arangodb: 573 | interface: arangodb 574 | require-auth-proxy: 575 | interface: auth_proxy 576 | require-autoscaling: 577 | interface: autoscaling 578 | require-aws-iam: 579 | interface: aws-iam 580 | require-aws-integration: 581 | interface: aws-integration 582 | require-azure-integration: 583 | interface: azure-integration 584 | require-barbican-hsm: 585 | interface: barbican-hsm 586 | require-barbican-secrets: 587 | interface: barbican-secrets 588 | require-baremetal: 589 | interface: baremetal 590 | require-bgp: 591 | interface: bgp 592 | require-bind-rndc: 593 | interface: bind-rndc 594 | require-block-storage: 595 | interface: block-storage 596 | require-cassandra: 597 | interface: cassandra 598 | require-catalogue: 599 | interface: catalogue 600 | require-ceilometer: 601 | interface: ceilometer 602 | require-ceph-admin: 603 | interface: ceph-admin 604 | require-ceph-bootstrap: 605 | interface: ceph-bootstrap 606 | require-ceph-client: 607 | interface: ceph-client 608 | require-ceph-dashboard: 609 | interface: ceph-dashboard 610 | require-ceph-iscsi-admin-access: 611 | interface: ceph-iscsi-admin-access 612 | require-ceph-mds: 613 | interface: ceph-mds 614 | require-ceph-osd: 615 | interface: ceph-osd 616 | require-ceph-radosgw: 617 | interface: ceph-radosgw 618 | require-ceph-rbd-mirror: 619 | interface: ceph-rbd-mirror 620 | require-certificate-transfer: 621 | interface: certificate_transfer 622 | require-cinder: 623 | interface: cinder 624 | require-cinder-backend: 625 | interface: cinder-backend 626 | require-cinder-backup: 627 | interface: cinder-backup 628 | require-cinder-ceph-key: 629 | interface: cinder-ceph-key 630 | require-cinder-gw: 631 | interface: cinder-gw 632 | require-cinder-nedge: 633 | interface: cinder-nedge 634 | require-cloudflared-route: 635 | interface: cloudflared-route 636 | require-config-server: 637 | interface: config-server 638 | require-container-runtime: 639 | interface: container-runtime 640 | require-containerd: 641 | interface: containerd 642 | require-containers: 643 | interface: containers 644 | require-cos-agent: 645 | interface: cos_agent 646 | require-cos-k8s-tokens: 647 | interface: cos-k8s-tokens 648 | require-dashboard: 649 | interface: dashboard 650 | require-dashboard-plugin: 651 | interface: dashboard-plugin 652 | require-db: 653 | interface: db 654 | require-db2: 655 | interface: db2 656 | require-designate: 657 | interface: designate 658 | require-dex-oidc-config: 659 | interface: dex-oidc-config 660 | require-dns-record: 661 | interface: dns_record 662 | require-dns-transfer: 663 | interface: dns_transfer 664 | require-docker-registry: 665 | interface: docker-registry 666 | require-dockerhost: 667 | interface: dockerhost 668 | require-elastic-beats: 669 | interface: elastic-beats 670 | require-elasticsearch: 671 | interface: elasticsearch 672 | require-elasticsearch-datastore: 673 | interface: elasticsearch-datastore 674 | require-ephemeral-backend: 675 | interface: ephemeral-backend 676 | require-etcd: 677 | interface: etcd 678 | require-etcd-proxy: 679 | interface: etcd-proxy 680 | require-event-service: 681 | interface: event-service 682 | require-external-cloud-provider: 683 | interface: external_cloud_provider 684 | require-external-provider: 685 | interface: external_provider 686 | require-fiveg-core-gnb: 687 | interface: fiveg_core_gnb 688 | require-fiveg-n2: 689 | interface: fiveg_n2 690 | require-fluentbit: 691 | interface: fluentbit 692 | require-forward-auth: 693 | interface: forward_auth 694 | require-ftn-compiler: 695 | interface: ftn-compiler 696 | require-gcp-integration: 697 | interface: gcp-integration 698 | require-generic-ip-port-user-pass: 699 | interface: generic-ip-port-user-pass 700 | require-github-runner-image-v0: 701 | interface: github_runner_image_v0 702 | require-giraph: 703 | interface: giraph 704 | require-glance: 705 | interface: glance 706 | require-glance-backend: 707 | interface: glance-backend 708 | require-glance-simplestreams-sync: 709 | interface: glance-simplestreams-sync 710 | require-glauth-auxiliary: 711 | interface: glauth_auxiliary 712 | require-gnocchi: 713 | interface: gnocchi 714 | require-grafana-auth: 715 | interface: grafana_auth 716 | require-grafana-cloud-config: 717 | interface: grafana_cloud_config 718 | require-grafana-dashboard: 719 | interface: grafana_dashboard 720 | require-grafana-datasource: 721 | interface: grafana_datasource 722 | require-grafana-datasource-exchange: 723 | interface: grafana_datasource_exchange 724 | require-grafana-metadata: 725 | interface: grafana_metadata 726 | require-grafana-source: 727 | interface: grafana-source 728 | require-guacd: 729 | interface: guacd 730 | require-hacluster: 731 | interface: hacluster 732 | require-haproxy-route: 733 | interface: haproxy-route 734 | require-heat-plugin-subordinate: 735 | interface: heat-plugin-subordinate 736 | require-http: 737 | interface: http 738 | require-http-proxy: 739 | interface: http_proxy 740 | require-httpd: 741 | interface: httpd 742 | require-hydra-endpoints: 743 | interface: hydra_endpoints 744 | require-influxdb-api: 745 | interface: influxdb-api 746 | require-infoblox: 747 | interface: infoblox 748 | require-ingress: 749 | interface: ingress 750 | require-ingress-auth: 751 | interface: ingress-auth 752 | require-ingress-per-unit: 753 | interface: ingress_per_unit 754 | require-irc-bridge: 755 | interface: irc_bridge 756 | require-istio-gateway-info: 757 | interface: istio-gateway-info 758 | require-java: 759 | interface: java 760 | require-jenkins-agent-v0: 761 | interface: jenkins_agent_v0 762 | require-jenkins-extension: 763 | interface: jenkins-extension 764 | require-jenkins-slave: 765 | interface: jenkins-slave 766 | require-k8s-cluster: 767 | interface: k8s-cluster 768 | require-k8s-service: 769 | interface: k8s-service 770 | require-kafka: 771 | interface: kafka 772 | require-kafka-client: 773 | interface: kafka_client 774 | require-kapacitor: 775 | interface: kapacitor 776 | require-karma-dashboard: 777 | interface: karma_dashboard 778 | require-keystone: 779 | interface: keystone 780 | require-keystone-admin: 781 | interface: keystone-admin 782 | require-keystone-credentials: 783 | interface: keystone-credentials 784 | require-keystone-domain-backend: 785 | interface: keystone-domain-backend 786 | require-keystone-fid-service-provider: 787 | interface: keystone-fid-service-provider 788 | require-keystone-middleware: 789 | interface: keystone-middleware 790 | require-keystone-notifications: 791 | interface: keystone-notifications 792 | require-kratos-info: 793 | interface: kratos_info 794 | require-kube-control: 795 | interface: kube-control 796 | require-kube-dns: 797 | interface: kube-dns 798 | require-kubeflow-dashboard-links: 799 | interface: kubeflow_dashboard_links 800 | require-kubernetes-cni: 801 | interface: kubernetes-cni 802 | require-kubernetes-info: 803 | interface: kubernetes-info 804 | require-landscape-hosted: 805 | interface: landscape-hosted 806 | require-ldap: 807 | interface: ldap 808 | require-livepatch-pro-airgapped-server: 809 | interface: livepatch-pro-airgapped-server 810 | require-lldp: 811 | interface: lldp 812 | require-loadbalancer: 813 | interface: loadbalancer 814 | require-local-monitors: 815 | interface: local-monitors 816 | require-login-ui-endpoints: 817 | interface: login_ui_endpoints 818 | require-logs: 819 | interface: logs 820 | require-logstash-client: 821 | interface: logstash-client 822 | require-loki-push-api: 823 | interface: loki_push_api 824 | require-lte-core: 825 | interface: lte-core 826 | require-lxd: 827 | interface: lxd 828 | require-lxd-bgp: 829 | interface: lxd-bgp 830 | require-lxd-dns: 831 | interface: lxd-dns 832 | require-lxd-https: 833 | interface: lxd-https 834 | require-lxd-metrics: 835 | interface: lxd-metrics 836 | require-magma-orchestrator: 837 | interface: magma-orchestrator 838 | require-manila-plugin: 839 | interface: manila-plugin 840 | require-matrix-auth: 841 | interface: matrix_auth 842 | require-memcache: 843 | interface: memcache 844 | require-midonet: 845 | interface: midonet 846 | require-mongodb: 847 | interface: mongodb 848 | require-mongodb-client: 849 | interface: mongodb_client 850 | require-monitor: 851 | interface: monitor 852 | require-monitors: 853 | interface: monitors 854 | require-mount: 855 | interface: mount 856 | require-munin-node: 857 | interface: munin-node 858 | require-mysql: 859 | interface: mysql 860 | require-mysql-async: 861 | interface: mysql_async 862 | require-mysql-async-replication: 863 | interface: mysql-async-replication 864 | require-mysql-client: 865 | interface: mysql_client 866 | require-mysql-monitor: 867 | interface: mysql-monitor 868 | require-mysql-root: 869 | interface: mysql-root 870 | require-mysql-router: 871 | interface: mysql-router 872 | require-mysql-shared: 873 | interface: mysql-shared 874 | require-nats: 875 | interface: nats 876 | require-nedge: 877 | interface: nedge 878 | require-neutron-api: 879 | interface: neutron-api 880 | require-neutron-load-balancer: 881 | interface: neutron-load-balancer 882 | require-neutron-plugin: 883 | interface: neutron-plugin 884 | require-neutron-plugin-api: 885 | interface: neutron-plugin-api 886 | require-neutron-plugin-api-subordinate: 887 | interface: neutron-plugin-api-subordinate 888 | require-nginx-route: 889 | interface: nginx-route 890 | require-nova: 891 | interface: nova 892 | require-nova-ceilometer: 893 | interface: nova-ceilometer 894 | require-nova-cell: 895 | interface: nova-cell 896 | require-nova-compute: 897 | interface: nova-compute 898 | require-nova-vgpu: 899 | interface: nova-vgpu 900 | require-nova-vmware: 901 | interface: nova-vmware 902 | require-nrpe: 903 | interface: nrpe 904 | require-nrpe-external-master: 905 | interface: nrpe-external-master 906 | require-ntp: 907 | interface: ntp 908 | require-oathkeeper-info: 909 | interface: oathkeeper_info 910 | require-oauth: 911 | interface: oauth 912 | require-object-storage: 913 | interface: object-storage 914 | require-odl-controller-api: 915 | interface: odl-controller-api 916 | require-oidc-client: 917 | interface: oidc-client 918 | require-openfga: 919 | interface: openfga 920 | require-opencti-connector: 921 | interface: opencti_connector 922 | require-opensearch-client: 923 | interface: opensearch_client 924 | require-openstack-integration: 925 | interface: openstack-integration 926 | require-openstack-loadbalancer: 927 | interface: openstack-loadbalancer 928 | require-ovsdb: 929 | interface: ovsdb 930 | require-ovsdb-cluster: 931 | interface: ovsdb-cluster 932 | require-ovsdb-cms: 933 | interface: ovsdb-cms 934 | require-ovsdb-manager: 935 | interface: ovsdb-manager 936 | require-ovsdb-subordinate: 937 | interface: ovsdb-subordinate 938 | require-pacemaker-remote: 939 | interface: pacemaker-remote 940 | require-parca-scrape: 941 | interface: parca_scrape 942 | require-parca-store: 943 | interface: parca_store 944 | require-peer-cluster: 945 | interface: peer_cluster 946 | require-pgsql: 947 | interface: pgsql 948 | require-placement: 949 | interface: placement 950 | require-pod-defaults: 951 | interface: pod-defaults 952 | require-postfix-metrics: 953 | interface: postfix-metrics 954 | require-postgresql-async: 955 | interface: postgresql_async 956 | require-postgresql-client: 957 | interface: postgresql_client 958 | require-prolog-epilog: 959 | interface: prolog-epilog 960 | require-prometheus: 961 | interface: prometheus 962 | require-prometheus-manual: 963 | interface: prometheus-manual 964 | require-prometheus-remote-write: 965 | interface: prometheus_remote_write 966 | require-prometheus-rules: 967 | interface: prometheus-rules 968 | require-prometheus-scrape: 969 | interface: prometheus_scrape 970 | require-public-address: 971 | interface: public-address 972 | require-quantum: 973 | interface: quantum 974 | require-rabbitmq: 975 | interface: rabbitmq 976 | require-radosgw-multisite: 977 | interface: radosgw-multisite 978 | require-radosgw-user: 979 | interface: radosgw-user 980 | require-ranger-client: 981 | interface: ranger_client 982 | require-redis: 983 | interface: redis 984 | require-register-application: 985 | interface: register-application 986 | require-rest: 987 | interface: rest 988 | require-s3: 989 | interface: s3 990 | require-saml: 991 | interface: saml 992 | require-script-provider: 993 | interface: script-provider 994 | require-sdcore-config: 995 | interface: sdcore_config 996 | require-sdn-plugin: 997 | interface: sdn-plugin 998 | require-secrets: 999 | interface: secrets 1000 | require-sentry-metrics: 1001 | interface: sentry-metrics 1002 | require-service-control: 1003 | interface: service-control 1004 | require-service-mesh: 1005 | interface: service-mesh 1006 | require-shards: 1007 | interface: shards 1008 | require-slurmd: 1009 | interface: slurmd 1010 | require-slurmdbd: 1011 | interface: slurmdbd 1012 | require-slurmrestd: 1013 | interface: slurmrestd 1014 | require-smtp: 1015 | interface: smtp 1016 | require-squid-auth-helper: 1017 | interface: squid-auth-helper 1018 | require-ssl-termination: 1019 | interface: ssl-termination 1020 | require-statistics: 1021 | interface: statistics 1022 | require-stun-server: 1023 | interface: stun-server 1024 | require-swift: 1025 | interface: swift 1026 | require-swift-global-cluster: 1027 | interface: swift-global-cluster 1028 | require-swift-gw: 1029 | interface: swift-gw 1030 | require-swift-proxy: 1031 | interface: swift-proxy 1032 | require-syslog: 1033 | interface: syslog 1034 | require-telegraf-exec: 1035 | interface: telegraf-exec 1036 | require-temporal: 1037 | interface: temporal 1038 | require-thruk-agent: 1039 | interface: thruk-agent 1040 | require-tls-certificates: 1041 | interface: tls-certificates 1042 | require-tokens: 1043 | interface: tokens 1044 | require-tracing: 1045 | interface: tracing 1046 | require-traefik-route: 1047 | interface: traefik_route 1048 | require-trino-client: 1049 | interface: trino_client 1050 | require-ubuntu: 1051 | interface: ubuntu 1052 | require-udldap-userdata: 1053 | interface: udldap-userdata 1054 | require-untrusted-container-runtime: 1055 | interface: untrusted-container-runtime 1056 | require-user-group: 1057 | interface: user-group 1058 | require-vault-autounseal: 1059 | interface: vault-autounseal 1060 | require-vault-kv: 1061 | interface: vault-kv 1062 | require-vsd-rest-api: 1063 | interface: vsd-rest-api 1064 | require-vsphere-integration: 1065 | interface: vsphere-integration 1066 | require-web-publish: 1067 | interface: web-publish 1068 | require-websso-fid-service-provider: 1069 | interface: websso-fid-service-provider 1070 | require-websso-trusted-dashboard: 1071 | interface: websso-trusted-dashboard 1072 | require-xlc-compiler: 1073 | interface: xlc-compiler 1074 | require-zookeeper: 1075 | interface: zookeeper 1076 | require-zuul: 1077 | interface: zuul 1078 | require-haproxy-route-tcp: 1079 | interface: haproxy-route-tcp 1080 | --------------------------------------------------------------------------------