Add a software runtime#

This guide walks through implementing a new software runtime plugin for Kiso (for example, Podman or Singularity).

Read How Kiso extensions work first for background on the extension model. Refer to the Software interface reference for complete method signatures.

Prerequisites#

  • Familiarity with Python dataclasses, Ansible, and EnOSlib

  • An existing Kiso development setup: pip install -e ".[all]" && pre-commit install

Step 1 — Create the plugin subpackage#

Create a top-level package directory for your plugin:

src/kiso_podman/
  __init__.py
  installer.py
  configuration.py
  schema.py
  main.yml           ← Ansible playbook

Step 2 — Define the configuration dataclass#

In configuration.py:

from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Podman:
    labels: list[str]
    version: Optional[str] = None

The dataclass fields become the config keys users write in their YAML. Field names use snake_case.

Step 3 — Define the JSON schema#

In schema.py:

schema = {
    "type": "object",
    "properties": {
        "labels": {
            "type": "array",
            "items": {"type": "string"},
            "minItems": 1,
        },
        "version": {
            "type": "string",
        },
    },
    "required": ["labels"],
    "additionalProperties": False,
}

The schema is used to validate user configs before provisioning starts.

Step 4 — Write the Ansible playbook#

In main.yml, write tasks to install the software:

- name: Install Podman
  hosts: "{{ labels | join(':') }}"
  become: true
  tasks:
    - name: Install Podman
      ansible.builtin.package:
        name: "podman{% if version is defined %}={{ version }}{% endif %}"
        state: present

The playbook receives the labels variable as a list of hostnames resolved by EnOSlib.

Note

To support Chameleon Edge, the steps to install the software have to be codes as shell commands too.

Step 5 — Implement the installer class#

In installer.py:

import logging
from pathlib import Path

from kiso import edge, utils

from kiso_podman.configuration import Podman
from kiso_podman.schema import SCHEMA

log = logging.getLogger(__name__)


class PodmanInstaller:
    schema: dict = SCHEMA
    config_type: type = Podman

    def __init__(self, config: Podman):
        self.config = config

    def check(self, label_to_machines: dict) -> None:
        """Validate that all referenced labels exist."""
        for label in self.config.labels:
            if label not in label_to_machines:
                raise ValueError(
                    f"Podman references label '{label}' which does not exist in sites"
                )

    def __call__(self, env) -> None:
        """Install Podman on nodes matching the configured labels."""
        log.info("Installing Podman on labels: %s", self.config.labels)
        labels = env["labels"]
        _labels = utils.resolve_labels(labels, self.config.labels)
        vms, containers = utils.split_labels(_labels, labels)
        results = []

        if vms:
            results.extend(
                utils.run_ansible([Path(__file__).parent / "main.yml"], roles=vms)
            )

        if containers:
            for container in containers:
                results.append(
                    edge.run_script(
                        container,
                        Path(__file__).parent / "podman.sh",
                        "--no-dry-run",
                        timeout=-1,
                    )
                )

        # Render the results

See also

See Kiso API reference to reuse code to upload files, download files, and run commands, request public IPs, etc.

Step 6 — Register the entry point#

In pyproject.toml, add the entry point under [project.entry-points."kiso.software"]:

[project.entry-points."kiso.software"]
podman = "kiso_podman.installer:PodmanInstaller"

Reinstall the package so the entry point is registered:

pip install -e ".[all]"

Step 7 — Verify the plugin loads#

kiso check experiment.yml

With a config that uses software.podman, the validator should accept it. An invalid config should be rejected.

Step 8 — Write tests#

Add tests in tests/software/ following the existing patterns in tests/software/docker/ and tests/software/apptainer/. Run:

pytest tests/

See also#