Metadata-Version: 2.4
Name: airpuls-sdk-cli
Version: 0.1.4
Summary: Command-line tool for airpuls Near-RT RIC xApps: project scaffolding, lifecycle, and deployment.
Author: airpuls GmbH
License-Expression: LicenseRef-CSSL-1.0
Project-URL: Homepage, https://airpuls.de
Keywords: oran,ric,near-rt-ric,xapp,cli,scaffolding
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Telecommunications Industry
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: System :: Networking
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: questionary>=2.0
Requires-Dist: jinja2>=3.1
Requires-Dist: jsonschema>=4.21
Requires-Dist: ruamel.yaml>=0.18
Provides-Extra: runtime
Requires-Dist: airpuls-ric-sdk>=0.1.4; extra == "runtime"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"

# `airpuls-sdk` — command-line tool for airpuls Near-RT RIC xApps

Scaffolds, validates, runs, packages, and deploys xApp projects for the
airpuls Near-RT RIC.  Distributed as `airpuls-sdk-cli`; the command it
installs is `airpuls-sdk`.

```bash
pip install airpuls-sdk-cli
```

Pure Python, published as a `py3-none-any` wheel: the tool installs
wherever Python 3.9+ runs, so a project can be created from any
developer machine.  Linux and macOS are the supported platforms for the
full workflow — see [Platform support](#platform-support).

## Two distributions, two jobs

| Distribution | What it is | Platforms |
|---|---|---|
| `airpuls-sdk-cli` | this tool — scaffolding, lifecycle, deployment | Linux, macOS (installs anywhere Python runs) |
| `airpuls-ric-sdk` | the xApp runtime: Python bindings for the C client library | Linux x86_64 and aarch64 (binary wheels) |

Most commands need only the CLI.  Running an xApp locally —
`xapp run`, and `xapp test` for suites that import the SDK — needs the
runtime in the same environment; install it where that applies:

```bash
pip install airpuls-sdk-cli[runtime]   # Linux x86_64 / aarch64
```

On macOS and Windows the runtime has no wheel, which is what the
generated Dev Container is for: `xapp new` writes a `.devcontainer/`
whose Dockerfile pins an x86_64 Debian base, reproducing the
environment the runtime needs, so the project is created natively and
run inside the container.  `airpuls-sdk doctor` reports which half is
present, and `xapp run` explains the missing runtime rather than
failing on an import error.

The container's architecture is the `PLATFORM` build argument in the
generated `.devcontainer/devcontainer.json`, and it is a trade-off:
`linux/amd64` (the default) matches the runtime wheel so the xApp runs
and tests inside the container, at emulation speed on an arm64 host;
`linux/arm64` is native and covers everything that does not execute the
xApp, since no arm64 runtime wheel exists.  Creating the container
succeeds either way — the arm64 case reports why the runtime is absent
instead of failing.

## Platform support

| | Linux | macOS | Windows |
|---|---|---|---|
| install, `xapp new`, `validate`, `info`, `build`, `add sink`, `config`, `doctor` | yes | yes | yes |
| `xapp deploy`, `undeploy`, `credentials` | yes | yes | SSH keys only |
| `xapp run`, `xapp test` | yes (x86_64) | Dev Container | Dev Container |
| `xapp package` | needs docker | needs docker | needs docker |

The deployment commands drive the OpenSSH tools.  Windows ships an
OpenSSH client, so key-based authentication works, but three things do
not: the interactive password fallback (it uses the POSIX
`SSH_ASKPASS` mechanism), the one-time `ssh-copy-id` offer, and the
restrictive mode applied to a fetched client key.  A Windows user
therefore needs a key the deployment host already trusts.  CI runs the
suite on Linux; macOS is covered by development use, and Windows is not
exercised at all.

## Quick start

```bash
airpuls-sdk xapp new my-xapp        # interactive wizard; --defaults for CI
                                    # (generates the project and git-inits it)
cd my-xapp
airpuls-sdk xapp validate           # xapp.yml against xapp.schema.json
airpuls-sdk xapp build              # per-language build, output captured
airpuls-sdk xapp test               # run the project's tests/
airpuls-sdk xapp run                # foreground run against the RIC
airpuls-sdk xapp package            # <name>-<lang>:latest on airpuls-xapp-sdk
airpuls-sdk xapp info               # project summary
airpuls-sdk xapp add sink --file …  # configure a telemetry destination
airpuls-sdk xapp deploy             # entry → nrtric-ctrl host over SFTP
airpuls-sdk xapp undeploy           # remove the entry from the host
airpuls-sdk xapp credentials        # register identity + fetch mTLS material
airpuls-sdk config ric add lab …    # register a RIC (several allowed)
airpuls-sdk doctor                  # environment diagnostics
```

## Telemetry sinks

An xApp emits into two channels — *timeseries* for numeric samples and
*structured* for event records — and `xapp add sink` configures a
destination for them in `xapp.yml`:

```bash
airpuls-sdk xapp add sink                              # asks what to add
airpuls-sdk xapp add sink --file ./events.jsonl        # structured
airpuls-sdk xapp add sink --redis redis://host:6379/0  # both channels
airpuls-sdk xapp add sink --influxdb http://host:8086  # timeseries
```

Invoked without a destination on a terminal, the command asks which one
to add and for the values it needs, each with its default in brackets.
The InfluxDB organization and bucket default to what the deployment
provisions — its InfluxDB setup creates a fixed `xapp_metrics` bucket
for xApps beside the RIC's own — so a URL alone is a complete
destination; `--org` and `--bucket` override them.

A destination serving both channels is added to both; `--channel
timeseries|structured` narrows it.  Only the keys the destination
determines are written — every other setting keeps its schema default —
and the manifest is validated before it is saved, so a sink never
leaves `xapp.yml` violating its own contract.  An already-configured
sink is reported rather than replaced, and comments in the file
survive the edit.

Credentials never appear on the command line or in the manifest:
`--token-env NAME` (InfluxDB, default `INFLUX_TOKEN`) and
`--password-env NAME` (Redis) record the *name* of the environment
variable holding the secret.  A secret typed as an argument would be
kept in the shell history and written into `xapp.yml`, which is
committed with the project and uploaded to the deployment host, so a
value that is not a variable name is refused rather than stored — and
the prompts ask for the variable name too, never the secret.  Add a
destination without switching it on with `--disabled`.

## Deployment

Deployment follows the nrtric-ctrl pattern: every generated project
includes an executable `<name>.xapp` docker-run launcher, and
`xapp deploy` uploads the entry the stack consumes — launcher,
`xapp.yml`, `xapp.schema.json` — into `<nrtric-ctrl>/xapps/<name>/` on
a registered host.  The project source is never uploaded; the runtime
artifact is the docker image the launcher references (`--image` on
`xapp new`, default the local `xapp package` tag).  The image reaches
the host through a registry, or directly with
`xapp deploy --with-image` (`docker save` streamed into `docker load`
over SSH).

Several RICs can be registered (`config ric add/list/use/remove`);
operations pick one with `--ric`, the registry default, or an
interactive list.  Transfers use the OpenSSH tools: key authentication
first (an identity file can be stored per RIC or passed per command),
and when the host denies it on an interactive terminal the CLI asks for
the SSH password — masked, held only in process memory for that
invocation, fed to OpenSSH through askpass so it never reaches a
command line or a log — while `config ric add` offers a one-time
`ssh-copy-id` that makes every later connection passwordless.

## TLS

tls:// is first-class through the whole flow, and the RIC's own
listener configuration is the source of truth for the transport:

- `xapp new` with a `tls://` endpoint emits the `ric.tls` file set the
  schema requires, at the paths the launcher mounts, and registers the
  xApp's identity with the RIC when a target is already configured.
- `xapp deploy` reads the RIC's listener configuration before
  uploading.  Against a tls:// listener it mints the client certificate
  (CN = deployment name, the RIC's identity binding), places CA,
  certificate, and key beside the deployed launcher, and — when the
  project still dials tcp:// — adapts the uploaded `xapp.yml` copy so a
  scheme mismatch cannot be deployed.  Against a plaintext listener a
  tls:// project is refused: the CLI never downgrades a secured
  configuration.
- `xapp credentials` registers the identity and fetches CA + client
  pair into the project's `certs/` directory, which `xapp run` picks up
  automatically.

## Output

Subcommand output is never passed through to the terminal: the CLI
captures it, reports the outcome with its cause and an excerpt on
failure, and writes the full transcript under the project's
`.airpuls/logs/`.  Exit codes are typed: 1 generic, 2 usage,
3 validation, 4 subprocess, 5 toolchain/environment.

### JSON output for editor integrations

`--json`, placed before the command (or `AIRPULS_SDK_JSON=1`), makes
every command write exactly one JSON document on stdout instead of
rendered text:

```bash
airpuls-sdk --json xapp info
airpuls-sdk --json doctor          # one record per environment check
airpuls-sdk --json config ric list # registered targets, for a picker
```

```json
{"schema": 1, "ok": true, "command": "xapp info",
 "data": {"name": "my-xapp", "endpoint": "tls://ric:36422", "…": "…"},
 "warnings": []}
```

`schema`, `ok`, `command`, `data`, and `warnings` are common to every
command; `data` fields are per command.  A failure adds an `error`
block — `kind` (`usage`, `validation`, `subprocess`, `toolchain`,
`interrupted`, `internal`), `message`, `cause`, `hint`, `detail`, and
the transcript path in `log` — and the process exit status is the same
in both modes, so a caller can branch on the status and read the
document only when it is non-zero.

`--json --help` answers with the command surface as data — the
subcommands a group holds, and the arguments and options a command
accepts, with their defaults and required flags — so an integration can
build its own affordances without scraping help text.

The mode also makes every command non-interactive: nothing prompts, so
a missing answer fails with a usage error naming the option to pass
(`xapp new` needs its options or `--defaults`, `xapp undeploy` needs
`--yes`) rather than blocking on a question nothing can answer.
Warnings become `warnings` entries instead of terminal lines, and the
streamed output of `xapp run` moves to stderr, keeping stdout a single
document; its transcript path is in `data.log`.

## Development

The suite runs without the xApp runtime and without a native build:

```bash
pip install -e .[test]
pytest tests
```
