4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
"""Helpers to select instances from a ``resolved_integrations`` dict.
|
|
|
|
``classify_integrations`` publishes:
|
|
- ``resolved[service]`` — flat config dict of the DEFAULT (first) instance
|
|
(backward compat with all code written before multi-instance support)
|
|
- ``resolved[f"_all_{service}_instances"]`` — list of all instances as
|
|
``[{"name": str, "tags": dict, "config": dict, "integration_id": str}, ...]``
|
|
(only present when > 1 instance OR an instance has a non-``default`` name)
|
|
|
|
These helpers give consumers a clean API for multi-instance selection. They
|
|
never mutate the input and tolerate missing keys (returning ``None`` or ``[]``
|
|
as appropriate).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
def _instances_key(service: str) -> str:
|
|
return f"_all_{service}_instances"
|
|
|
|
|
|
def _as_dict(config: Any) -> dict[str, Any] | None:
|
|
"""Normalize a config value (dict or BaseModel) to a plain dict, or None."""
|
|
if isinstance(config, BaseModel):
|
|
return config.model_dump(exclude_none=True)
|
|
if isinstance(config, dict):
|
|
return config
|
|
return None
|
|
|
|
|
|
def get_instances(resolved: dict[str, Any] | None, service: str) -> list[dict[str, Any]]:
|
|
"""Return all instance entries for ``service``.
|
|
|
|
When a service has only one instance and that instance has the default
|
|
name, ``_all_{service}_instances`` is omitted for tidiness. In that
|
|
case we synthesise a single-entry list from the flat ``resolved[service]``
|
|
so callers can treat single- and multi-instance cases uniformly.
|
|
"""
|
|
if not resolved:
|
|
return []
|
|
explicit = resolved.get(_instances_key(service))
|
|
if isinstance(explicit, list):
|
|
return [item for item in explicit if isinstance(item, dict)]
|
|
flat = resolved.get(service)
|
|
flat_dict = _as_dict(flat)
|
|
if flat_dict is not None:
|
|
return [
|
|
{
|
|
"name": "default",
|
|
"tags": {},
|
|
"config": flat_dict,
|
|
"integration_id": str(flat_dict.get("integration_id", "")),
|
|
}
|
|
]
|
|
return []
|
|
|
|
|
|
def get_default_instance(resolved: dict[str, Any] | None, service: str) -> dict[str, Any] | None:
|
|
"""Return the flat config dict for the default (first) instance, or None."""
|
|
if not resolved:
|
|
return None
|
|
flat = resolved.get(service)
|
|
return _as_dict(flat)
|
|
|
|
|
|
def get_instance_by_name(
|
|
resolved: dict[str, Any] | None, service: str, name: str
|
|
) -> dict[str, Any] | None:
|
|
"""Return the config dict of the instance named ``name``, or None."""
|
|
target = (name or "").strip().lower()
|
|
if not target:
|
|
return None
|
|
for inst in get_instances(resolved, service):
|
|
if str(inst.get("name", "")).lower() == target:
|
|
return _as_dict(inst.get("config"))
|
|
return None
|
|
|
|
|
|
def get_instances_by_tag(
|
|
resolved: dict[str, Any] | None, service: str, key: str, value: str
|
|
) -> list[dict[str, Any]]:
|
|
"""Return config dicts of every instance whose ``tags[key] == value``."""
|
|
target_key = (key or "").strip().lower()
|
|
target_value = (value or "").strip().lower()
|
|
if not target_key or not target_value:
|
|
return []
|
|
out: list[dict[str, Any]] = []
|
|
for inst in get_instances(resolved, service):
|
|
tags = inst.get("tags", {}) if isinstance(inst.get("tags"), dict) else {}
|
|
if tags.get(target_key) == target_value:
|
|
config = _as_dict(inst.get("config"))
|
|
if config is not None:
|
|
out.append(config)
|
|
return out
|
|
|
|
|
|
def select_instance(
|
|
resolved: dict[str, Any] | None,
|
|
service: str,
|
|
*,
|
|
name: str | None = None,
|
|
tags: dict[str, str] | None = None,
|
|
) -> dict[str, Any] | None:
|
|
"""Select an instance by name, then by tags, then default.
|
|
|
|
``name`` takes precedence if supplied. If ``name`` is set but no match is
|
|
found, return None (do NOT silently fall back to default — callers who
|
|
want that can chain to ``get_default_instance``). If ``tags`` is supplied
|
|
without ``name``, return the first instance whose tags are a superset of
|
|
the filter. If neither is supplied, return the default instance.
|
|
"""
|
|
if name:
|
|
return get_instance_by_name(resolved, service, name)
|
|
if tags:
|
|
normalized = {str(k).strip().lower(): str(v).strip().lower() for k, v in tags.items()}
|
|
for inst in get_instances(resolved, service):
|
|
inst_tags = inst.get("tags", {}) if isinstance(inst.get("tags"), dict) else {}
|
|
if all(inst_tags.get(k) == v for k, v in normalized.items()):
|
|
return _as_dict(inst.get("config"))
|
|
return None
|
|
return get_default_instance(resolved, service)
|
|
|
|
|
|
__all__ = [
|
|
"get_default_instance",
|
|
"get_instance_by_name",
|
|
"get_instances",
|
|
"get_instances_by_tag",
|
|
"select_instance",
|
|
]
|