Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/check-public.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fi

# IPv4 addresses other than 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24,
# loopback and 0.0.0.0.
ips=$(grep -rnoE --exclude-dir=.git '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' . \
ips=$(grep -rnoE --exclude-dir=.git --exclude-dir=__pycache__ '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' . \
| grep -vE ':(192\.0\.2|198\.51\.100|203\.0\.113)\.[0-9]+$|:127\.[0-9.]+$|:0\.0\.0\.0$' || true)
if [ -n "$ips" ]; then
echo "$ips"
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ project adheres to [Semantic Versioning](https://semver.org/).

### Added

- The connector SDK (`unified_connector.py`) and the `kv` example gitops connector.
- The connector SDK (the `unified_connectors` package) and the `kv` example gitops connector.
- SDK helpers for inventory connectors: settings from the source's config as strings or JSON, and per-system credential sets.
- `centreon`: Centreon monitored hosts as an Ansible inventory, with their services when asked.
- `cisco_ucs`: Cisco UCS Manager blades as an Ansible inventory, servers and spares apart.
Expand Down
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ vmware_vcenter/
└── fixtures/ # recorded API responses, anonymised, so tests run offline
```

`unified_connector.py` at the root is the shared SDK. Inventory connectors read their settings with
its `settings`, `as_list`, `as_bool` and `as_int` (unified-api passes every `config:` value as a
string) and find per-system credentials with `credential_sets`. Code two connectors need goes in a
module at the root too, never in a vendor folder.
`unified_connectors/` is the shared SDK: a package every connector imports after putting the
repository root on its path, laid out like a connector otherwise (its code in
`unified_connectors.py`, a README, `tests/`). Inventory connectors read their settings with its
`settings`, `as_list`, `as_bool` and `as_int` (unified-api passes every `config:` value as a
string) and find per-system credentials with `credential_sets`. Code two connectors need goes in
the SDK too, never in a vendor folder.

## Connector rules

Expand Down
4 changes: 2 additions & 2 deletions centreon/centreon.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@
import urllib.request
from concurrent.futures import ThreadPoolExecutor

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import ConnectorError, as_bool, as_int, as_list, settings
from unified_connectors import ConnectorError, as_bool, as_int, as_list, settings

PAGE_SIZE = 1000
# A configuration page costs about the same at 5000 rows as at 1000.
Expand Down
4 changes: 2 additions & 2 deletions cisco_ucs/cisco_ucs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
import xml.etree.ElementTree as ET
from urllib.parse import urlparse

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import (
from unified_connectors import (
ConnectorError,
as_bool,
as_int,
Expand Down
4 changes: 2 additions & 2 deletions dell_avamar/dell_avamar.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlencode, urlparse

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import (
from unified_connectors import (
ConnectorError,
as_bool,
as_int,
Expand Down
4 changes: 2 additions & 2 deletions device42/device42.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import ConnectorError, as_bool, as_int, settings
from unified_connectors import ConnectorError, as_bool, as_int, settings

log = logging.getLogger("device42")

Expand Down
4 changes: 2 additions & 2 deletions infinidat_infinibox/infinidat_infinibox.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
import urllib.request
from urllib.parse import urlencode, urlparse

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import ConnectorError, as_bool, as_int, settings
from unified_connectors import ConnectorError, as_bool, as_int, settings

PAGE_SIZE = 1000
GROUP_PREFIX = "iboxarray_"
Expand Down
4 changes: 2 additions & 2 deletions kv/kv.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import os
import sys

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import ConnectorError, run
from unified_connectors import ConnectorError, run

# What this connector manages: each type of object, the field that identifies
# one, and what must exist first. Entries live in a namespace, so namespaces
Expand Down
4 changes: 2 additions & 2 deletions pure_flasharray/pure_flasharray.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
from concurrent.futures import ThreadPoolExecutor
from urllib.parse import quote, urlparse

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import (
from unified_connectors import (
ConnectorError,
as_bool,
as_int,
Expand Down
51 changes: 51 additions & 0 deletions unified_connectors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# unified_connectors

The shared SDK every connector in this repository imports. Standard library only, Python 3.11+.

```python
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connectors import (
ConnectorError,
as_bool,
as_list,
credential_sets,
settings,
)
```

## Inventory connectors (unified-api)

| Helper | What it does |
| ------ | ------------ |
| `settings()` | The source's `config:` as a dict, from `SOURCE_CONFIG`; refuses anything but a JSON object |
| `as_list(value, default)` | A list from a comma-separated string (`"a, b"`) or a JSON list |
| `as_bool(value, default)` | `true`, `1` and `yes` in any case are true |
| `as_int(value, default)` | A whole number, or a clear error |
| `credential_sets(prefix, required, optional)` | Every complete `<PREFIX>_<TAG>_<FIELD>` set, unified-api's `CREDENTIAL_` form first |

unified-api passes every `config:` value as a string, so a connector reads `domains: "a,b"` with
`as_list` and `insecure: "true"` with `as_bool`.

## Gitops connectors (unified-gitops)

A gitops connector declares the kinds it manages and hands two functions to `run`:

```python
run(
kinds={
"namespace": {"key": "name"},
"entry": {"key": "name", "depends_on": ["namespace"]},
},
list_objects=list_objects, # (kind, config, credentials) -> the objects that exist now
apply=apply, # (kind, op, obj, config, credentials) -> create, update or delete one
)
```

`run` answers `describe`, `list --kind K` and `apply --kind K`, reads `APPLICATION_CONFIG` and
the `CREDENTIAL_*` variables, refuses to write unless `UNIFIED_ALLOW_WRITE=1`, and prints JSON on
stdout with exit 0 only after a whole answer. `kv/` is a complete example.

## Errors

Raise `ConnectorError("...")` for a failure worth one clean line on stderr; the connector exits 1.
23 changes: 23 additions & 0 deletions unified_connectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""The shared SDK every connector imports; the code is in unified_connectors.py."""

from .unified_connectors import (
PROTOCOL,
ConnectorError,
as_bool,
as_int,
as_list,
credential_sets,
run,
settings,
)

__all__ = [
"PROTOCOL",
"ConnectorError",
"as_bool",
"as_int",
"as_list",
"credential_sets",
"run",
"settings",
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import pytest

ROOT = Path(__file__).resolve().parent.parent
FOLDER = Path(__file__).resolve().parent.parent
spec = importlib.util.spec_from_file_location(
"unified_connector", ROOT / "unified_connector.py"
"unified_connectors", FOLDER / "unified_connectors.py"
)
sdk = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sdk)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A gitops connector describes the kinds of object it manages, writes two
functions and hands them to run:

from unified_connector import ConnectorError, run
from unified_connectors import ConnectorError, run

def list_objects(kind, config, credentials):
return [{"name": "a"}] # what exists now
Expand Down
4 changes: 2 additions & 2 deletions vmware_vcenter/vmware_vcenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
from pyVim.connect import Disconnect, SmartConnect
from pyVmomi import vim, vmodl

# The SDK sits at the repository root, one folder up.
# The SDK, the unified_connectors package, sits at the repository root.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unified_connector import (
from unified_connectors import (
ConnectorError,
as_bool,
as_int,
Expand Down
Loading