Skip to main content

Crate pethost

Crate pethost 

Source
Expand description

Pethost’s API for Rust: a Client with a method per call, and the API’s messages as plain structs in types.

use pethost::types::{DeployProjectRequest, FileChange, FileChangeChange, GetMachineRequest};
use pethost::{Client, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    // An API token from the panel's Settings, kept out of the code.
    let token = std::env::var("PETHOST_TOKEN").expect("PETHOST_TOKEN is not set");
    let pethost = Client::new(&token);

    // The machine, and every project on it.
    let response = pethost.get_machine(GetMachineRequest::default()).await?;
    if let Some(machine) = &response.machine {
        println!("{}", machine.hostname);
    }
    for project in &response.projects {
        println!("{}", project.project_id);
    }

    // A new version of the project `notes`: one file written, the others as they are.
    let request = DeployProjectRequest {
        project_id: "notes".into(),
        files: vec![FileChange {
            path: "/hello.txt".into(),
            change: Some(FileChangeChange::Text("Hello from the Pethost SDK\n".into())),
            ..Default::default()
        }],
        ..Default::default()
    };
    let response = pethost.deploy_project(request).await?;

    // The machine refuses files it cannot run, and says what to write instead.
    for violation in &response.violations {
        println!("{}: {}", violation.location, violation.violation_message);
    }
    if let Some(operation) = &response.operation {
        println!("{:?}", operation.status);
    }
    if let Some(project) = &response.project {
        println!("{}", project.url);
    }
    Ok(())
}

§How the API reads in Rust

  • A request is a struct literal ending in ..Default::default(). A later version of the crate adds fields; a literal that ends so keeps compiling.
  • A field is a plain value, and its zero means absent: 0, false, "", an empty Vec, an enum’s Unspecified. Such a field is not sent. An Option marks the fields where absent says something else, and every message and Timestamp: there None is absent, and Some(0) is a zero that is sent.
  • A oneof is an enum named after its message and itself, held in an Option: None = no member is set.
  • The API grows, and an older crate keeps reading it. Enums are #[non_exhaustive]: a value added later reads as Unrecognized(number), a oneof’s member added later as Unrecognized, and a field added later is not seen. Structs that only come back from the API are #[non_exhaustive] too: read their fields, and start from Default::default() to make one.
  • A failed call is an Error: its code to branch on, its message for people, and what the API attached as ErrorDetail.
  • A stream is a ResponseStream, which its method returns at once: its next is each response as the API sends it, then the Error of a stream that failed, and dropping it ends the call. The crate does not call again by itself: a response’s cursor goes into the next call’s request.
  • JSON is the API’s. Every message and enum of types, and a Timestamp, is serde’s Serialize and Deserialize: serde_json::to_string(&response) is the JSON the API answers over HTTP, with the proto’s field names, and reading refuses a field this version does not know. types says the rest.
  • Async, on Tokio. Pages, retries and waiting are the caller’s loops: a response’s next_page_token goes into the next request’s page_token.

§The API

Pethost is managed hosting for Docker Compose projects on a machine of your own. A program does with this crate what the web panel and an agent do: it reads the machine and its projects, deploys, reads logs and HTTP traffic, runs commands in containers and moves files.

The API’s own documentation follows; the comments of the methods and the types are its words.

§Model

Machine: one VDS running the daemon, petnode; every call acts on the caller’s.

  • Project (project_id): a Docker Compose project, a directory with compose.yaml at its root, deployed whole. People see metadata.name.
  • Service (service): one container. The person opens a shell in it from their terminal with ssh <service>.<project_id>@<Machine.hostname> (Service::ssh_command), once RunMachineActionRequestAction::AddSshKey added their public key. Nobody logs in to the machine itself.
  • Volume (volume): a named volume. Survives deploys and removal from compose.yaml. All else a container writes is lost when it is recreated.
  • Host (host): a hostname the proxy serves over HTTPS, routing paths to services: any <name>.<Machine.apps_domain>, the person’s domain on Pethost, or a domain they own.
  • Operation (operation_id): a deploy, recreate, backup, restore or delete, with a log. The call that starts one waits for its end and answers with the project as it then is.
  • Snapshot (snapshot_id): an off-machine backup of the directory and volumes, nightly and on demand, only while Machine::backups_enabled: never assume one.

§The project is its directory

Its files declare everything (services, images, volumes, environment, hosts, name), and compose.yaml runs as written, or is refused with violations, each saying what to write: the machine corrects nothing in it. Source:

  • files: on the machine. Client::deploy_project changes them, replaces them with an archive, or puts an earlier deploy’s back.
  • github: a repository directory at a commit, fetched by the machine. Client::deploy_project deploys a commit; auto_deploy follows the branch (GithubSource).

/.env and compose.yaml’s x-pethost carry over between versions: after the first deploy, a new archive’s or commit’s own are ignored. Change them with Client::deploy_project files (/.env) and x_pethost.

§Conventions

Modules§

types
The API’s messages, enums and oneofs.

Structs§

Client
The Pethost API of one account: a method per call.
Error
Why a call failed: a code to branch on, a message for people, and what the API attached.
ResponseStream
The responses of a stream’s call, each as the API sends it.
Timestamp
A moment in time as the API holds it: seconds and nanoseconds since 1970-01-01T00:00:00Z.

Enums§

ErrorCode
The error codes of the Connect protocol. The crate’s front page says what each means in this API.
ErrorDetail
What the API attaches to an Error to branch on, beyond its code.