Skip to content

Latest commit

 

History

52 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rossoctl-cli

A command-line interface for Rossoctl, built with Cobra.

Install

The downloadRossoctl script downloads the release archive for your platform extracts it, and installs the binary at $HOME/.config/rossoctl/rossoctl:

curl -fsSL https://raw.githubusercontent.com/rossoctl/rossoctl-cli/main/downloadRossoctl | sh
PATH=$PATH:$HOME/.config/rossoctl
# alternately, sudo mv $HOME/.config/rossoctl /usr/local/bin

Quick usage, for shared OpenShift Rossoctl API servers

# (Choose w3id for shared cluster login)
rossoctl --server https://rossoctl-ui-rossoctl-system.apps.ykt3.hcp.res.ibm.com/api/v1 login
rossoctl agents list

Quick usage, for existing Kind cluster Rossoctl API server

rossoctl login
rossoctl agents list

Running a command behind an AuthBridge pipeline

# `--config` is required and takes a local YAML file or a URL serving YAML (a
# remote config is fetched to a temp file, which is removed on exit). Everything
# after `--` is passed through to the command untouched, and rossoctl exits with
# the command's exit status.
rossoctl authbridge exec --config ./authbridge.yaml -- claude "explain this repo"
rossoctl authbridge exec --config https://example.com/authbridge.yaml -- ./script.sh --verbose

What authbridge exec starts, driven by the config:

Config What starts
listener.roles includes forward forward proxy on listener.forward_proxy_addr (feeds traffic through the outbound pipeline)
tls_bridge.mode not disabled/empty TLS bridge, so the pipeline sees decrypted HTTPS instead of an opaque CONNECT tunnel
session.enabled not false session store, plus the session API on listener.session_api_addr when set

A listen address of port 0 is resolved to the port the kernel actually assigned, so an ephemeral proxy is still dialable by the hosted command.

--sessionServer overrides the session API address when given explicitly (defaulting to localhost:9094, which leaves the config's own address alone):

# Serve the session API somewhere else, even if the config disabled sessions.
rossoctl authbridge exec --sessionServer 127.0.0.1:9500 --config ./authbridge.yaml -- claude

# Turn session tracking off entirely.
rossoctl authbridge exec --sessionServer "" --config ./authbridge.yaml -- claude

The command's environment is pointed at whatever started: HTTP_PROXY for the forward proxy, plus HTTPS_PROXY and the CA trust variables (NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, SSL_CERT_FILE) when the TLS bridge runs. Variables already set in your environment are left alone. Everything is shut down when the command exits or on SIGINT/SIGTERM.

--with-claude-otel additionally exports the variables that make Claude Code send traces to the local collector — CLAUDE_CODE_ENABLE_TELEMETRY=1, CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1, OTEL_EXPORTER_OTLP_PROTOCOL=http/json, OTEL_TRACES_EXPORT_INTERVAL=1, OTEL_TRACES_EXPORTER=otlp, OTEL_LOGS_EXPORTER=none, OTEL_METRICS_EXPORTER=none, and OTEL_EXPORTER_OTLP_ENDPOINT built from httpEndpoint in ~/.config/rossoctl/otel-config.yaml:

rossoctl otel collect                      # writes otel-config.yaml
rossoctl authbridge exec --with-claude-otel --config ./authbridge.yaml -- claude

It reads that record rather than assuming a port, so it fails — naming rossoctl otel collect — when no collector has been started on this host, instead of pointing the command at an endpoint that is not listening.

--otel-endpoint overrides that address with a full URL, for a collector this host did not start. The record is then not read at all, so it needs no local collector:

rossoctl authbridge exec --with-claude-otel \
    --otel-endpoint https://otel.example.com:4318 --config ./authbridge.yaml -- claude

The value must begin with http:// or https://, and it sets one of the variables --with-claude-otel turns on — so passing it without that flag is an error rather than an implied opt-in.

Authbridge's own log output goes to --logfile (default /tmp/authbridge.log) rather than stderr, so it does not interleave with the hosted command's output. The path is printed at startup; pass --logfile "" to log to stderr instead.

Plugins are compiled in via one blank import per plugin, matching the authbridge binaries — including context-guru, which those binaries keep opt-in but which rossoctl links by default so the context-guru demos run without a special build. Drop any of them with its exclude tag:

go build -tags exclude_plugin_opa,exclude_plugin_contextguru

Note that go run reports its own exit status, not the command's, so it collapses any non-zero status to 1. Use a built binary when the exit code matters.

Usage

rossoctl --help
rossoctl version
rossoctl agents --help

# Print instructions for installing the platform (clone + per-cluster setup script)
rossoctl install

# Manage contexts (persisted in ~/.config/rossoctl/config.yaml, kubectl-style)
# get-contexts never creates anything, so it prints an empty table until some
# other command seeds the config. Seeding creates two contexts: one for the
# default API server, which becomes current, and a local "cortex" one.
rossoctl config get-contexts
rossoctl config create-context --name dev \
    --server http://my-host:8080/api/v1/ --namespace team1 --bearer-token <token>   # becomes current
rossoctl config use-context dev
rossoctl config set-context --namespace team1   # set namespace on current context (warns if unknown to server)
rossoctl config set-context --namespace team1 --server http://other:8080/api/v1/   # also replace the server
rossoctl config set-context --name prod          # rename the current context (updates the current reference)
rossoctl login --token <token>                  # set the token on the current context directly
rossoctl login                                  # or: OAuth device flow against the server's Keycloak
rossoctl login --server http://host:8080/api/v1/ --token <token>   # target the context for that host (create if absent), make it current
# The "cortex" context is answered inside the command's own process, from the
# records `authbridge exec` wrote, so it needs no server and no token. --cortex
# makes it current without contacting anything; its namespace comes from those
# local records.
rossoctl login --cortex                         # switch to the local cortex; no network, no token
# `cortex serve` and `authbridge exec` both create that context if it is missing,
# so `login --cortex` is usually unnecessary. `cortex serve` also makes it current;
# `authbridge exec` deliberately does not, since it hosts an unrelated command and
# must not repoint later invocations. Switch back with `config use-context`.

# Inspect the bearer token stored on the context (decoded locally; nothing is sent)
rossoctl auth status                            # name/username/email, issuer, expiry, audiences, roles, scopes
rossoctl auth status --json                     # the decoded claims as JSON
rossoctl auth status --context prod             # inspect another context's token

# Print the raw bearer token, and nothing else, for use in another command.
# Note this writes a credential to stdout: a terminal keeps it in scrollback and
# CI keeps it in the build log. Exits non-zero when the context holds no token,
# so the substitution fails rather than expanding to an empty string.
rossoctl auth token
curl -H "Authorization: Bearer $(rossoctl auth token)" http://my-host:8080/api/v1/agents?namespace=team1
rossoctl auth token --context prod              # the token from another context

# Show the server's auth configuration (GET <server>/auth/config)
rossoctl auth-config
rossoctl auth-config --json
rossoctl --server http://my-host:8080/api/v1/ auth-config

# Show current session + platform status, mirroring the web UI admin page
# (GET <server>/auth/status, /auth/me, /config/platform-status)
rossoctl status
rossoctl status --json                          # raw API data as JSON

# List agents (GET <server>/agents)
rossoctl agents list                            # single namespace: agents --namespace, else current context
rossoctl agents --namespace team2 list          # list one specific namespace
rossoctl agents list --all-namespaces           # -A: discover via GET /namespaces, list across all
rossoctl agents list --all-namespaces --json    # each namespace's response, separated by ---
rossoctl agents list --no-headers               # omit the header row, for piping to other tools
# With --no-headers the "no agents found" notice goes to stderr instead, so
# stdout is empty when there is nothing to list and a pipeline sees no rows:
rossoctl agents list --no-headers | awk '{print $1}' | xargs -n1 rossoctl agents delete

# Show one agent (GET <server>/agents/<namespace>/<name>)
rossoctl agents get orders                      # single-column text, laid out like the web detail page
rossoctl agents get orders --json               # raw JSON

# Wait for an agent to become ready, polling GET <server>/agents/<namespace>/<name>
# every 2 seconds. Exits 0 as soon as it is ready, so it can gate what follows.
rossoctl agents wait orders
rossoctl agents wait orders --timeout 5m        # default 60s; --timeout 0 waits indefinitely
rossoctl agents wait orders -v                  # report progress on stderr while waiting

rossoctl agents import from-image --name orders --containerImage ghcr.io/x/y:latest \
    && rossoctl agents wait orders --timeout 5m \
    && ./run-integration-tests.sh
# Waiting ends early and non-zero when readiness will never arrive: a failed job or
# a rollout past its deadline, or a name the server does not know (404). Reporting
# that immediately beats spending the timeout to report the wrong cause.

# Show an agent's AuthBridge configuration
# (GET <server>/agents/<namespace>/<name>/identity-config): the mode, plus the
# inbound and outbound plugin pipelines in execution order, each plugin with its
# on_error policy and per-instance config.
rossoctl agents authbridge get orders
rossoctl agents authbridge get orders --json    # raw JSON

# Set an agent's AuthBridge configuration
# (PUT <server>/agents/<namespace>/<name>/identity-config). --policy-file is
# required; its bytes are sent verbatim as text/plain, so comments and key order
# survive and the server validates rather than the CLI.
rossoctl agents authbridge set orders --policy-file ./authbridge.yaml

# --wait reads the configuration before writing, then polls every 2 seconds until
# what AuthBridge reports differs from that baseline, giving up after 2 minutes.
# The comparison is against the baseline, not the file: the file is YAML written to
# a ConfigMap while the GET returns the live JSON a sidecar serves, redacted.
# Because a change is the signal, re-applying the configuration already in effect
# cannot be confirmed — it times out and exits non-zero, saying so.
rossoctl agents authbridge set orders --policy-file ./authbridge.yaml --wait

# Delete an agent (DELETE <server>/agents/<namespace>/<name>)
rossoctl agents delete orders

# Import an agent from a container image (POST <server>/agents)
rossoctl agents import from-image --name orders --containerImage ghcr.io/x/y:latest
rossoctl agents import --deployment-type sandbox from-image \
    --name orders --containerImage ghcr.io/x/y:latest --imagePullSecret regcred \
    --envVarsURL https://example.com/orders.env   # newline-separated key=value
# --envVar sets one variable inline and may be repeated. Values are taken
# literally, commas and all. Where both name the same variable, --envVar wins
# over --envVarsURL, whichever order the flags appear in.
rossoctl agents import from-image --name orders --containerImage ghcr.io/x/y:latest \
    --envVar LOG_LEVEL=debug --envVar 'TAGS=a,b,c'
# --additionalParameterJSON sends request fields the CLI has no flag for. Its value
# is a JSON dict, or the name of a file containing one — a value starting with '{'
# is the document itself, anything else is a filename.
rossoctl agents import from-image --name orders --containerImage ghcr.io/x/y:latest \
    --additionalParameterJSON '{"serviceAccount":"orders-sa"}'
# Repeatable: the dicts are merged, a later one winning a key they share, and the
# result is overlaid onto the request body. Merging is by top-level key, so a
# repeated key is replaced whole rather than combined with what it had. Keys that
# name a field the other flags set — containerImage here — override them.
rossoctl agents import from-image --name orders --containerImage ghcr.io/x/y:latest \
    --additionalParameterJSON ./base.json \
    --additionalParameterJSON '{"containerImage":"ghcr.io/x/y:pinned"}'

# `agents --namespace` overrides the context's namespace for any agents subcommand
rossoctl agents --namespace team2 get orders    # -> GET /agents/team2/orders

# `--context` is a top-level flag: it uses a named context instead of the current
# one (its server, token, namespace). It may appear before or after the subcommand.
rossoctl --context prod agents get orders
rossoctl agents --context prod get orders               # equivalent
rossoctl --context prod agents --namespace teamX list   # --namespace still overrides the context's namespace

# Tools mirror the agents commands, against the /tools endpoint.
# --namespace, --context, --all-namespaces (-A), --json, and --no-headers behave
# as for agents.
rossoctl tools list                              # single namespace (context, or --namespace)
rossoctl tools list --all-namespaces             # discover and list across all
rossoctl tools list --no-headers | awk '{print $1}' | xargs -n1 rossoctl tools delete
rossoctl tools --namespace team2 list --json
rossoctl tools get weather-mcp                   # GET /tools/<namespace>/weather-mcp (single-column detail)
rossoctl tools get weather-mcp --json            # raw JSON response
rossoctl tools wait weather-mcp                  # poll until ready; default --timeout 60s
rossoctl tools delete weather-mcp                # DELETE /tools/<namespace>/weather-mcp
rossoctl tools import from-image --name weather-mcp --containerImage ghcr.io/x/y:latest  # POST /tools
rossoctl tools import --deployment-type statefulset from-image \
    --name weather-mcp --containerImage ghcr.io/x/y:latest --envVarsURL https://example.com/tool.env
# --envVar works as it does for agents: repeatable, literal values, and it wins over --envVarsURL
rossoctl tools import from-image --name weather-mcp --containerImage ghcr.io/x/y:latest \
    --envVar LOG_LEVEL=debug --envVar 'TAGS=a,b,c'
# --ports sets service ports as name:port:targetPort[:protocol] (default http:9090:9090:TCP); a bare "port" = http:port:port:TCP
rossoctl tools import from-image --name weather-mcp --containerImage ghcr.io/x/y:latest --ports grpc:9000:9001:TCP,8080
# --additionalParameterJSON works as it does for agents: a JSON dict or a file
# containing one, repeatable, merged by top-level key, and winning over the flags above
rossoctl tools import from-image --name weather-mcp --containerImage ghcr.io/x/y:latest \
    --additionalParameterJSON '{"serviceAccount":"mcp-sa"}' --additionalParameterJSON ./extra.json

# A tool built from source reports "Building" until its build finishes, which is
# often longer than the 60s default, so allow for it. A failed build reports
# "Build Failed" and ends the wait immediately rather than burning the timeout on
# a workload that will never be created.
rossoctl tools wait weather-mcp --timeout 10m
# Against the local "cortex" context `tools wait` runs to its timeout: that server
# does not implement the tool detail endpoint it polls (`tools get` fails there for
# the same reason), so use `tools list` to check a local tool. `agents wait` works.

# Run a local OpenTelemetry collector that forwards traces to MLflow. Generates a
# collector config under ~/.config/rossoctl/otel and starts
# otel/opentelemetry-collector-contrib with it mounted, receiving OTLP on 4317
# (gRPC) and 4318 (HTTP). Needs docker or podman on PATH.
rossoctl otel collect
# --traces_endpoint sets the otlphttp/mlflow exporter's traces_endpoint. The
# default reaches the host from inside the container, where "localhost" would mean
# the collector itself.
rossoctl otel collect --traces_endpoint http://host.containers.internal:5002/v1/traces
# MLflow has to be listening for spans to arrive. When nothing is on the endpoint's
# port, the command says so — and still starts the collector, since the exporter
# retries and MLflow can be started afterwards:
#   mlflow server --host 0.0.0.0 --port 5001 --allowed-hosts '*'
# Both flags matter: MLflow otherwise binds loopback, which a container cannot
# reach, and rejects requests whose Host header is host.containers.internal.
#
# The generated config's path and the address a client uses to reach the receiver
# (httpEndpoint: 127.0.0.1:4318 — the dialable form, not the 0.0.0.0 the receiver
# binds inside the container) are recorded in ~/.config/rossoctl/otel-config.yaml.
# The container runs detached; stop it with `podman stop` (or `docker stop`) using
# the name printed on start.

# Send one mock span to that collector, to check the path from here to MLflow
# without an instrumented workload. Random trace and span IDs each run, so every
# invocation is a distinct trace; the span ends now and started one second ago.
rossoctl otel send-mock-trace
# --serviceName sets the span's service.name resource attribute, which is what a
# trace backend groups by — so it is what the span is listed under in MLflow.
rossoctl otel send-mock-trace --serviceName my-agent
# --url overrides where it posts (default http://localhost:4318/v1/traces), for a
# collector whose published port was remapped or that runs on another host.
rossoctl otel send-mock-trace --url http://localhost:14318/v1/traces

# List namespaces (GET <server>/namespaces)
rossoctl namespaces list
rossoctl namespaces list --all      # include non-rossoctl-enabled namespaces
rossoctl namespaces list --json

# Log the underlying REST requests to stderr
rossoctl -v agents list

Tests

make test                      # go test ./...
make vet
gofmt -l .                     # prints files needing formatting; make fmt rewrites them
go test ./... -race -count=1
go test ./... -count=1 -shuffle=on

The suite needs no services, credentials, or network access: tests bind ephemeral localhost ports and point HOME at a temp directory, and the container tests assert on the command strings they would run rather than invoking a real runtime.

.github/workflows/ci.yml runs exactly these on every pull request and on pushes to main, plus a go mod tidy check. Shuffled order is included because the suite mutates process state (HOME, cobra flag values), so an order-dependent test is a real risk — see the pflag hazard documented in cmd/root_test.go.

Full docs

See the documentation

About

A CLI (command line interface) for rossoctl

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages