# orca.remote

> A module of morca's API.

The `orca` command against a deployed `orca ui`, over HTTPS, as a user of it: log in once, then act from anywhere the UI can be reached.

```
orca login https://orca.mlake.dev     # asks for a name and a password
orca --remote orca.mlake.dev backfill gharchive 2025-01-02..2025-01-31
orca --remote orca.mlake.dev pause gharchive
orca --remote orca.mlake.dev status gharchive
orca logout https://orca.mlake.dev    # revokes the token
```

`--remote <url>` (or `--url`), else `ORCA_REMOTE` (or `ORCA_URL`), picks
the UI; without one every command goes to `ORCA_DATABASE_URL`, as it always
has. A bare host is `https://<host>`. Plain `http` is refused but to
`127.0.0.1`, `localhost` and `[::1]`, where nothing is on the wire: a token,
like a password, never crosses a network in the clear.

`orca login` posts the name and password to `/api/login`, which checks
them as `/login` does, under the same limit on failures, and answers an API
token (`orca.tokens`). The password is read without echo from the terminal,
or as a line of standard input when that isn't one (`--user` names the
user then). The token is kept in `ORCA_CREDENTIALS`, by default
`~/.config/orca/credentials` (`$XDG_CONFIG_HOME/orca` if set), a line per
UI: its URL, the user, when the token expires, and the token. The file is
written with mode 600 in a directory made with mode 700, and one that
others can read is refused, as ssh refuses a key.

The commands that can go to a UI are `backfill` (`--rerun`, and
`--preview`, which queues nothing), `start`, `cancel` (a pipeline's keys,
or one run), `pause` and `unpause`, `resume` and `rerun` of a run,
`rebuild`, and the reads `status`, `lineage`, `watchdog status` and
`alerts`. An action is a `POST` of
JSON to the path the UI's own button posts to, with `Authorization: Bearer
<token>`, so it's the same code, the same checks and the same row of
`orca.audit` as the button's, from `cli`. Reads are the pages' JSON,
which needs no login, and they send no token. Each prints what the command
prints over the database. The token is never in a URL, an argument or a
message.

## Structs

### `Credential`

```mojo
struct Credential
```

Implements: `Copyable`, `Movable`

Fields:

- `url: String`
- `user: String`
- `expires: String`
- `token: String`

### `Remote`

```mojo
struct Remote
```

A UI at `url`, as `user` with `token` (empty for reads).

Implements: `Movable`

Fields:

- `url: String`
- `token: String`
- `http: HttpClient`

#### `__init__`

```mojo
def __init__(out self, url: String, token: String = "")
```

#### `get`

```mojo
def get(self, path: String) -> String
```

#### `post`

```mojo
def post(self, path: String, body: String) -> String
```

### `Api`

```mojo
struct Api
```

What the commands here read and do: a UI's JSON (`remote`), or the same pages and actions over the database, in this process.

Implements: `Movable`

Fields:

- `remote: Optional[Remote]`
- `db_url: String`

#### `__init__`

```mojo
def __init__(out self, var remote: Remote)
```

```mojo
def __init__(out self, db_url: String)
```

#### `get`

```mojo
def get(mut self, path: String) -> String
```

The JSON `GET path` answers (`path` has its query).

#### `post`

```mojo
def post(mut self, path: String, fields: List[Tuple[String, String]]) -> String
```

The JSON of the action `path` with `fields`, or raises why it was refused.

## Functions

### `is_remote_command`

```mojo
def is_remote_command(cmd: String) -> Bool
```

Whether `--remote` can run `cmd`; the others need the database.

### `remote_url`

```mojo
def remote_url(given: String) -> String
```

`given` as a UI's URL, `scheme://host[:port]` in lower case without a default port, or raises why it's refused: anything but `https`, but `http` to this machine; a path, query, fragment or user in it.

### `remote_target`

```mojo
def remote_target(mut args: List[String]) -> String
```

The UI the command line picks, `--remote <url>` (or `--url`) right after `orca`, which this removes from `args`, else `ORCA_REMOTE` (or `ORCA_URL`); empty for none.

### `credentials_path`

```mojo
def credentials_path() -> String
```

`ORCA_CREDENTIALS`, else `orca/credentials` in `XDG_CONFIG_HOME`, else in `~/.config`.

### `read_credentials`

```mojo
def read_credentials(path: String) -> List[Credential]
```

The logins kept in `path`, none if there's no file. Raises if others may read it.

### `write_credentials`

```mojo
def write_credentials(path: String, creds: List[Credential])
```

Writes `creds` to `path` with mode 600, in a directory made with mode 700 if there's none: a new file, created under umask 077 and moved into place, so the token is never in a file others could read.

### `find_credential`

```mojo
def find_credential(creds: List[Credential], url: String) -> Int
```

The index of `url`'s login in `creds`, or -1.

### `login_command`

```mojo
def login_command(args: List[String], target: String) -> Int
```

`orca login [<url>] [--user NAME] [--label L] [--days N]`.

### `logout_command`

```mojo
def logout_command(args: List[String], target: String) -> Int
```

`orca logout [<url>]`: revokes the token there, and forgets it.

### `remote_api`

```mojo
def remote_api(url: String) -> Api
```

`url`'s UI as the login kept for it, or with no token if there's none (reads need none).

### `pressure_status`

```mojo
def pressure_status(v: Value) -> String
```

`orca status`'s line for the scheduler's host's memory pressure (`memory_pressure` in `/`'s JSON, `orca.admission`), or empty for none.

### `print_status`

```mojo
def print_status(json: String, one: Bool)
```

`orca status`'s lines from `/`'s JSON, or with `one` a pipeline's page's: its line, any sensing error, and its latest runs.

### `alert_lines`

```mojo
def alert_lines(json: String) -> List[String]
```

`orca alerts`' lines from `/api/alerts`' JSON: a line per alert, newest first, its time, kind, state and delivery, then its title.

### `api_command`

```mojo
def api_command(mut api: Api, args: List[String]) -> Int
```

Runs `args` (`orca <command> ...`) through `api`, printing what the command prints over the database.
