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.
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.
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 environment that runs the project. xapp new
provisions exactly that by default: it creates the project's .venv
and installs requirements.txt (production dependencies — the
runtime on Linux) and tests/requirements.txt (pytest and the CLI
the generated tests import) into it — --no-venv skips the step,
and xapp venv provisions an existing checkout at any time. The
commands that execute project code resolve their interpreter in that
order — an activated virtual environment first, then the project's
.venv, then the interpreter running the CLI — so a provisioned
project runs correctly whether the CLI is invoked from a shell or by
an editor. Installing the runtime beside the CLI works too:
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 is built for the host's architecture, so it is native
on an Apple-silicon Mac as well as on x86_64, and the runtime installs
in both — the whole workflow runs inside it. The PLATFORM build
argument in the generated .devcontainer/devcontainer.json forces a
specific architecture when one is needed, at emulation speed.
Platform support
| Linux | macOS | Windows | |
|---|---|---|---|
install, xapp new, validate, info, build, add sink, ric, config, doctor |
yes | yes | yes |
xapp connect, deploy, undeploy, credentials |
yes | yes | SSH keys only |
xapp run, xapp test |
yes | Dev Container | Dev Container |
xapp build --docker-image |
needs docker | needs docker | needs docker |
xapp deploy builds the launcher's image, so it needs docker as
well — except with --no-image, which uploads the entry alone.
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
airpuls-sdk xapp new my-xapp # interactive wizard; --defaults for CI
# (picks the RIC — endpoint, connection and
# credentials come from it — generates,
# git-inits, and provisions .venv;
# --ric NAME skips the question, --no-venv
# the environment)
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 — after the readiness check, offering each fix in turn
airpuls-sdk xapp check # ready to run? endpoint, transport, TLS material, sink credentials — and the fix per row
airpuls-sdk xapp run --docker # the same from the built image, mounts done for you; --build builds it first
airpuls-sdk xapp build --docker-image # the image; --tag names it, --base-image swaps the SDK base
airpuls-sdk xapp info # project summary
airpuls-sdk xapp add sink --file … # configure a telemetry destination
airpuls-sdk xapp connect # connect the project to a registered RIC (endpoint, binding, credentials)
airpuls-sdk xapp deploy # build + push the image, entry → the host
airpuls-sdk xapp config render --deploy # the deploy template deploy uploads
airpuls-sdk xapp undeploy # remove the entry from the host
airpuls-sdk xapp credentials # register identity + fetch mTLS material and sink credentials
airpuls-sdk ric add lab … # register a RIC (several allowed)
airpuls-sdk doctor # environment diagnostics
Inside an xApp project doctor also reports the project environment:
whether .venv is provisioned, and whether requirements.txt and
tests/requirements.txt are satisfied by what is installed there — a
fresh clone warns until ./bootstrap-env.sh or airpuls-sdk xapp venv
runs.
A clone starts without the CLI, so every generated project ships
bootstrap-env.sh, a bash script that checks the prerequisites (Python
3.9+ with its venv module and ensurepip, the runtime wheel's
platform, docker and ssh as optional), names the system package to
install for anything missing without installing it, creates .venv,
installs the CLI and — through xapp venv — the project's
requirements, then runs doctor and xapp validate, runs xapp test
where the runtime is installed, and prints the commands up to the
first xapp run. --check reports the prerequisites alone; sourced
(source ./bootstrap-env.sh) it ends with .venv active in the shell.
xapp build --docker-image and xapp deploy build the project's image on the
airpuls/airpuls-xapp-sdk base image, checking exactly the reference
the project's Dockerfile FROM names (default latest;
xapp new --base-tag pins an older release). An absent base image is
reported with its remedy instead of surfacing as a docker build
failure, and on a terminal the command offers to pull it from Docker
Hub. Declining leaves the docker pull in the message, next to the
alternative of building it from an airpuls-ric checkout.
airpuls-sdk --install-completion installs the shell hook (bash,
zsh, fish, PowerShell): commands, options and paths complete through
the parser, the registered RIC names complete wherever a command takes
one, and the scaffold's choice sets complete on xapp new. The
callbacks read local state only — a completion never touches the
network.
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:
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 builds the image that launcher references, pushes it to
the registry the reference names, then 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 image is the runtime artifact, and the
host pulls it.
xapp.yml is the developer's working file — the RIC they reach, their
sinks, their paths — and every local command uses it as-is. The
uploaded xapp.yml is the deploy template the deployment renders
per run, derived from that file by a fixed mapping: the endpoint host
becomes {{ RIC_IP }} (scheme and port kept — the deployment switches
the scheme to the listener's), the ric.tls material paths become the
{{ DEFAULT_*_FILE_PATH }} tokens, xapp.instance becomes
{{ INSTANCE_ID }}, a sink URL on the RIC host or on the loopback
dials {{ RIC_IP }}, a relative file-sink path moves onto
/xapp/data (the directory the launcher mounts from the entry), and
the Redis and InfluxDB credentials name REDIS_PASSWORD and
INFLUX_TOKEN, the variables the deployment injects. Everything
else is carried verbatim, comments included; a sink dialling another
host or an absolute path is kept and named. xapp config render --deploy prints that template (--output FILE writes it).
The image is tagged with the launcher's own reference, so what is
pushed is what the host resolves. That reference must name a registry
— registry.airpuls.de/xapps/<name>:latest, given by --image on
xapp new. The scaffold default (<name>-<lang>:latest) names none,
so it would push to Docker Hub; xapp deploy refuses it and says so.
When the registry rejects the push for want of credentials, the error
names the docker login to run.
Two alternatives to the registry:
| flag | effect |
|---|---|
--image-copy |
build, then stream the image into the host's daemon over SSH (docker save | docker load) — for a host that cannot reach the registry; the image must be built for the host's architecture, a mismatch is refused |
--no-image |
upload the entry alone, building and publishing nothing — a configuration-only redeploy |
Several RICs can be registered (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 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 newagainst a RIC whose listener is tls:// emits theric.tlsfile set the schema requires, at the paths the launcher mounts, with the listener identity asserver_name, and registers the xApp's identity with that RIC at creation;--endpoint tls://…by hand does the same once exactly one registered RIC dials that host, and otherwise leaves the registration toxapp credentials.xapp deployreads the RIC's listener configuration before uploading. Against a tls:// listener it generates the client certificate (CN = deployment name, the RIC's identity binding) and places CA, certificate, and key beside the deployed launcher; the template keeps the project's scheme and the deployment switches it to tls:// when it renders the entry, so a scheme mismatch cannot be deployed. Against a plaintext listener a tls:// project is refused: the CLI never downgrades a secured configuration.xapp connectconnects the project to a registered RIC: it writesric.endpointand, against a tls:// listener, reads that listener's server certificate to record its Common Name asric.tls.server_name— so verification holds even when the certificate's subject alternative names do not cover the address the xApp dials (the read is advisory: when it fails the endpoint is still written, with a warning). It records the RIC as the project's RIC in the gitignored.airpuls/ric.yml—credentials,deploy,undeployand the run's own checks use it without asking;--ricoverrides per command — and syncs what a local run needs from it: the TLS identity and material for a tls:// listener, the sink credentials for either transport (--no-syncwrites the endpoint alone). A connected project keeps itself in sync:xapp add sinkfetches the credential a new sink reads, and a run that finds sink credentials or TLS material missing fetches them from the project's RIC before launching. The deploy template carries a recorded identity verbatim; the deployment's own dial name matches the RIC certificate, so an entry without one verifies as well.xapp credentialsregisters the identity and fetches CA + client pair into the project'scerts/directory, whichxapp runpicks up automatically. It also reads the deployment's.envon the host and writes the Redis password and InfluxDB token to the project's.airpuls/sinks.env(owner-only;REDIS_PASSWORD,INFLUX_TOKEN), whichxapp runandxapp run --dockerload — a variable set in the shell wins.--no-sinksleaves them on the host. That file is the deployment's secrets in clear text on a developer machine: development environments only, never a production RIC, never committed — the scaffold's.gitignoreexcludes.airpuls/andcerts/;xapp.ymlstays committed and names only the variables.
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:
airpuls-sdk --json xapp info
airpuls-sdk --json doctor # one record per environment check
airpuls-sdk --json ric list # registered targets, for a picker
{"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:
pip install -e .[test]
pytest tests
Metadata
Release files for airpuls-sdk-cli 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| airpuls_sdk_cli-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for airpuls_sdk_cli-0.3.0-py3-none-any.whl |
Release files / airpuls_sdk_cli-0.3.0-py3-none-any.whl
| Download URL | airpuls_sdk_cli-0.3.0-py3-none-any.whl |
|---|---|
| Size | 169.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
808706e64f4e99d238c8c8fb2203e724b9c211b5630479699b6b7466dbeeae76
|
|
BLAKE2b-256 checksum How to use checksums |
d6021a7598e8f78535f41984389c03d124c55d8f4ddba37fcd30864382d7b07f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Security for airpuls-sdk-cli 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.