Metadata-Version: 2.4
Name: lvi-script-sandbox-utils
Version: 0.7.1
Summary: Convenience utilities for LogicVein Script Sandbox Python scripts
License-Expression: Apache-2.0
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Dynamic: license-file

# LogicVein Script Sandbox Utils

Convenience helpers for Python scripts that LogicVein Playbooks run in the
Script Sandbox container. They wrap the sandbox's file-and-stdout contract:
a Playbook hands your script its inputs in `in.json`, and your script returns
results by printing JSON to stdout.

## Installation

From PyPI:

```bash
pip install lvi-script-sandbox-utils
```

From TestPyPI (pre-release / testing):

```bash
pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  lvi-script-sandbox-utils
```

The sandbox runs scripts in a container, so the package has to be available
inside it. If your sandbox mounts a packages directory (for example
`./packages:/packages`), install into it:

```bash
pip install --target ./packages lvi-script-sandbox-utils
```

The library has no third-party dependencies (standard library only).

## Usage

```python
import lvi_script_sandbox_utils as lvi

text = lvi.get_result()                       # the previous node's output
lvi.log("checking", lvi.device.hostname)      # debugging message

lvi.set_globals(
    line_count=len(text.splitlines()),
    **lvi.device.to_globals(["hostname", "model"]),
)
```

### Read the previous node's output

`lvi.get_result()` returns the previous node's result, such as a CLI
command's text. In a Playbook's first script node there is none, and it
returns `""` (or `get_result(default=...)`).

### Read the device

`lvi.device` is the Playbook's device. Its properties use LogicVein's names:
`hostname`, `ipAddress`, `model`, `osVersion`, `serialNumber`, `custom1`
.. `custom5`, `uuid`, and so on (your editor can list them). It's built the
first time you use it, and is the same object every time.

- Unset values are `None`.
- End-of-life/sale fields (`endOfLife`, `endOfSale`, `softwareEndOfLife`,
  `softwareEndOfSale`) are `YYYY-MM-DD` dates, and `lastBackup`,
  `lastChange`, `lastDiscovery`, `lastTelemetry` are `YYYY-MM-DDTHH:MM:SSZ`
  timestamps, all in UTC.
- `traits` lists the device's capabilities (`ssh`, `snmp`, `ncm`, ...).
- `lvi.device.to_dict()` gives all properties as a dict.

### Read other Playbook variables

```python
lvi.get_global("hostname")             # KeyError, listing what is set, if missing
lvi.get_global("site", default="")
lvi.get_globals()                      # all of them, as a dict
lvi.metadata.playbook                  # details of this run: playbook, executor,
lvi.metadata.execution_id              # execution_id, job_id, node, started_at, ...
```

### Return results

Call `lvi.set_globals(...)` **once**, at the end of the script, with
everything the Playbook should get back as global variables. Calling it (or
`set_raw()`) a second time raises `RuntimeError`, so build up results in a
dict and pass `**results`.

Playbook globals are strings. Numbers and booleans are fine, `None` is sent
as `""`, and a list, dict, device or metadata object is sent as its JSON
text, which a later script node can read back with
`json.loads(lvi.get_global(name))`. For a different format, convert it
yourself, e.g. `traits=", ".join(lvi.device.traits)`.

To return device properties, use `lvi.device.to_globals()`, all of them, or
`to_globals(["hostname", "model"])` for some. It names `hostname` and
`network` `device_hostname` and `device_network`, so they don't overwrite the
Playbook's own `hostname` and `network` globals.

`lvi.set_raw(text)` returns text as the node's raw output instead.

### Debugging

A script can't print while it runs in a Playbook: stdout carries the
results the Playbook reads, and the Script Sandbox fails the run if the
script writes anything to stderr. That includes Python warnings, which go to
stderr too.

Use `lvi.log(...)` instead. It takes the same arguments as `print()`, keeps
the message, and `set_globals()` returns all of them, one per line, as the
`script_log` global, so they show up with the node's results. Messages are
lost if the script never reaches `set_globals()`, and `set_raw()` doesn't
return them.

```python
lvi.log("parsed", len(lines), "lines")
lvi.set_globals(total="61 MB")      # also sends script_log="parsed 12 lines"
```

To run a script outside a Playbook, save an `in.json` from a run and point
the package at it:

```bash
LVI_SANDBOX_INPUT=./samples/in.json python my_script.py
```

When `LVI_SANDBOX_INPUT` is set, `lvi.log()` also prints each message to the
terminal (stderr) as it happens.

`lvi.load_input()` returns the whole `in.json` as a dict, if you need
something the functions above don't cover. Treat it as read-only.

### Upgrading from 0.6 or earlier

0.7.0 renamed the API so that it reads the way scripts use it:

| Before | From 0.7.0 |
|---|---|
| `from logicvein import lvi_script_sandbox_utils as lvi` (0.1.x) | `import lvi_script_sandbox_utils as lvi` |
| `get_input("cli_output")` | `get_result()` |
| `get_input("device")` | `load_input()["device"]` |
| `get_device()`, `get_full_device()`, `device_full` | `device` (every property) |
| `device.device_hostname`, `device.device_network` | `device.hostname`, `device.network` |
| `""` for unset device properties | `None` (still sent as `""` by `set_globals()`) |
| `get_metadata()` | `metadata` |
| `flush_output(**values)`, `set_output(key, value)` | `set_globals(**values)`, once |
| `flush_output(**vars(device))` | `set_globals(**device.to_globals())` |
| `flush_string_output(text)` | `set_raw(text)` |

The package stopped shipping a `logicvein/` directory in 0.2.0: it collided
with `lvi-api-client`'s, so `pip install --target` skipped whichever was
installed second (or, with `--upgrade`, deleted the first).

## Development and releases

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and
[PUBLISHING.md](PUBLISHING.md) for how releases are published.
