openapi: 3.0.0 info: title: Orchard description: Orchard orchestration API version: 0.1.0 paths: /controller/info: get: summary: "Retrieve controller's information" tags: - controller responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ControllerInfo' /cluster-settings: get: summary: "Retrieve cluster settings" tags: - cluster-settings responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ClusterSettings' put: summary: "Update cluster settings" tags: - cluster-settings requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ClusterSettings' responses: '200': description: Cluster settings were successfully updated content: application/json: schema: $ref: '#/components/schemas/ClusterSettings' /service-accounts: post: summary: "Create a Service Account" tags: - service-accounts requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ServiceAccount' responses: '200': description: Service Account resource was successfully created content: application/json: schema: $ref: '#/components/schemas/ServiceAccount' '409': description: Service Account resource with with the same name already exists get: summary: "List Service Accounts" tags: - service-accounts responses: '200': description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/ServiceAccount' /service-accounts/{name}: parameters: - in: path name: name required: true schema: type: string get: summary: "Retrieve a Service Account" tags: - service-accounts responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ServiceAccount' '404': description: Service Account resource with the given name doesn't exist put: summary: "Update a Service Account" tags: - service-accounts requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ServiceAccount' responses: '200': description: Service Account object was successfully updated content: application/json: schema: $ref: '#/components/schemas/ServiceAccount' '404': description: Service Account resource with the given name doesn't exist delete: summary: "Delete a Service Account" tags: - service-accounts responses: '200': description: Service Account resource was successfully deleted '404': description: Service Account resource with the given name doesn't exist /workers: get: summary: "List Workers" tags: - workers responses: '200': description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/Worker' /workers/{name}: parameters: - in: path name: name required: true schema: type: string get: summary: "Retrieve a Worker" tags: - workers responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Worker' '404': description: Worker resource with the given name doesn't exist delete: summary: "Delete a Worker" tags: - workers responses: '200': description: Worker resource was successfully deleted '404': description: Worker resource with the given name doesn't exist /workers/{name}/port-forward: parameters: - in: path name: name required: true schema: type: string get: summary: "Port-forward to a worker using WebSocket protocol" tags: - workers parameters: - in: query name: port description: Worker's TCP port number to connect to schema: type: integer minimum: 1 maximum: 65535 required: true - in: query name: wait description: Duration in seconds for the worker to become available if it's not available already schema: type: integer minimum: 0 maximum: 65535 default: 10 required: false - in: header name: Connection description: WebSocket protocol required header required: true schema: type: string - in: header name: Upgrade description: WebSocket protocol required header required: true schema: type: string responses: '400': description: Invalid port specified '404': description: Worker resource with the given name doesn't exist '503': description: Failed to establish connection with the requested worker /vms: post: summary: "Create a VM" tags: - vms requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/VMMeta' - $ref: '#/components/schemas/VMSpec' responses: '200': description: VM resource was successfully created content: application/json: schema: $ref: '#/components/schemas/VM' '400': description: Invalid VM specification '409': description: VM resource with the same name already exists get: summary: "List VMs" tags: - vms parameters: - in: query name: filter description: "Filter VMs using `path=value` syntax; currently only `worker=` is supported to return VMs assigned to the given worker" schema: type: string required: false responses: '200': description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/VM' /vms/{name}: parameters: - in: path name: name description: VM name to retrieve required: true schema: type: string - in: query name: watch description: Watch for changes a VM resource and return them a stream of ADDED, MODIFIED and DELETED notifications schema: type: boolean get: summary: "Retrieve a VM" tags: - vms responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/VM' application/x-ndjson: schema: type: object properties: type: type: string enum: [ ADDED, MODIFIED, DELETED ] object: $ref: '#/components/schemas/VM' '404': description: VM resource with the given name doesn't exist put: summary: "Update a VM" tags: - vms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VMSpec' responses: '200': description: VM object was successfully updated content: application/json: schema: $ref: '#/components/schemas/VM' '400': description: Invalid VM specification '404': description: VM resource with the given name doesn't exist delete: summary: "Delete a VM" tags: - vms responses: '200': description: VM resource was successfully deleted '404': description: VM resource with the given name doesn't exist /vms/{name}/events: parameters: - in: path name: name required: true schema: type: string get: summary: "Retrieve events for a given VM" tags: - vms parameters: - in: query name: limit description: Maximum number of events to return. schema: type: integer minimum: 1 - in: query name: order description: Sort order of events; asc (default) or desc. schema: type: string enum: - asc - desc - in: query name: cursor description: Opaque cursor from the X-Next-Cursor response header. schema: type: string responses: '200': description: OK headers: X-Next-Cursor: description: Opaque cursor for the next page of events, if any. schema: type: string content: application/json: schema: $ref: '#/components/schemas/Events' '404': description: VM resource with the given name doesn't exist /vms/{name}/port-forward: parameters: - in: path name: name required: true schema: type: string get: summary: "Port-forward to a VM using WebSocket protocol" description: | Connect to a VM's TCP port, a declared host process, or its Tart Guest Agent. Specify exactly one of `port`, `hostProcess`, or `target=tart-guest-agent`. Connecting to Tart Guest Agent requires either the `compute:write` or `compute:connect:tart-guest-agent` role. tags: - vms parameters: - in: query name: port description: VM's TCP port number to connect to; mutually exclusive with `hostProcess` and `target`. schema: type: integer minimum: 1 maximum: 65535 required: false - in: query name: hostProcess description: Name of a host process declared in the VM's `hostProcesses` field; mutually exclusive with `port` and `target`. schema: type: string required: false - in: query name: target description: Forward to the specified target; mutually exclusive with `port` and `hostProcess`. schema: type: string enum: [tart-guest-agent] required: false - in: query name: wait description: Duration in seconds to wait for the VM to transition into "running" state if not already running. schema: type: integer minimum: 0 maximum: 65535 default: 10 required: false - in: header name: Connection description: WebSocket protocol required header required: true schema: type: string - in: header name: Upgrade description: WebSocket protocol required header required: true schema: type: string responses: '400': description: Invalid query parameter or ambiguous port-forward target specified '404': description: VM or requested host process doesn't exist '503': description: Failed to establish connection with the worker responsible for the specified VM /vms/{name}/exec: parameters: - in: path name: name required: true schema: type: string get: summary: "Execute a command inside a VM using WebSocket protocol" tags: - vms parameters: - in: query name: command description: Command to execute. schema: type: string minLength: 1 required: true - in: query name: interactive description: | Whether to allocate an interactive standard input for the command When enabled, make sure to close the standard input by sending a `ExecClientFrameStdin` frame with an empty data. Otherwise the command might never terminate waiting for the standard input to end. schema: type: boolean default: false required: false - in: query name: stdin deprecated: true description: | Deprecated alias for `interactive`. If both `interactive` and `stdin` are provided, their values must match. schema: type: boolean default: false required: false - in: query name: tty description: Whether to allocate a pseudo-terminal for the command schema: type: boolean default: false required: false - in: query name: rows description: Initial terminal row count when `tty=true` schema: type: integer minimum: 0 maximum: 4294967295 required: false - in: query name: cols description: Initial terminal column count when `tty=true` schema: type: integer minimum: 0 maximum: 4294967295 required: false - in: query name: env description: | Environment variables to expose to the command. Use deep object query syntax, for example `env[FOO]=bar&env[BAZ]=qux`. style: deepObject explode: true schema: type: object additionalProperties: type: string required: false - in: query name: workdir description: Working directory to switch to before starting the command schema: type: string required: false - in: query name: wait description: Duration in seconds for the VM to become available if it's not available already schema: type: integer minimum: 0 maximum: 65535 default: 10 required: false - in: header name: Connection description: WebSocket protocol required header required: true schema: type: string - in: header name: Upgrade description: WebSocket protocol required header required: true schema: type: string responses: '101': description: | The connection has been upgraded to WebSocket. Messages exchanged after upgrade: * Orchard Client → Orchard Controller: `ExecClientFrame` * Orchard Controller → Orchard Client : `ExecControllerFrame` content: application/json: schema: oneOf: - $ref: '#/components/schemas/ExecClientFrame' - $ref: '#/components/schemas/ExecControllerFrame' '400': description: Invalid parameters were supplied '404': description: VM resource with the given name doesn't exist '503': description: Controller failed to establish a connection with the VM /vms/{name}/ip: parameters: - in: path name: name required: true schema: type: string get: summary: "Resolve the VM's IP address on the worker" tags: - vms parameters: - in: query name: wait description: Duration in seconds to wait for the VM to transition into "running" state if not already running. schema: type: integer minimum: 0 maximum: 65535 default: 0 required: false responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/IP' '404': description: VM resource with the given name doesn't exist '503': description: Failed to resolve the IP address on the worker responsible for the specified VM components: schemas: Worker: title: Worker node type: object properties: name: type: string description: Node name resources: type: object description: | Dictionary that maps the resource name to the amount of this resource provided by the worker for running VMs. additionalProperties: type: integer VM: title: Virtual Machine type: object allOf: - $ref: '#/components/schemas/VMMeta' - $ref: '#/components/schemas/VMSpec' - $ref: '#/components/schemas/VMState' VMMeta: title: Virtual Machine Metadata type: object properties: name: type: string description: VM name example: macos-tahoe-base generation: type: number description: Incremented by the controller each time a VM's specification changes readOnly: true VMSpec: title: Virtual Machine Specification type: object properties: os: type: string description: | Operating system used by a VM. Set to `linux` to work around the Apple's limitation of 2 macOS VMs per host. This field cannot be changed after the VM is created. default: darwin enum: [ darwin, linux ] arch: type: string description: | Hardware architecture to use for a VM. This field cannot be changed after the VM is created. default: arm64 enum: [ arm64, amd64 ] runtime: type: string description: | Runtime to use for a VM. This field cannot be changed after the VM is created. default: tart enum: [ tart, vetu ] image: type: string description: VM image for this VM example: ghcr.io/cirruslabs/macos-tahoe-base:latest imagePullPolicy: type: string description: VM image pull policy default: IfNotPresent enum: [ IfNotPresent, Always ] cpu: type: number description: Number of CPUs assigned to this VM default: 4 memory: type: number description: Amount of RAM in megabytes assigned to this VM default: 8192 diskSize: type: number description: Disk size for this VM example: 100 endpoints: type: array description: | TCP or UDP services inside the VM to expose through ports on the Orchard Worker. The worker ports assigned to the endpoints are reported in `observedEndpoints`. Endpoint ports listen on all worker network interfaces. Use firewall rules to restrict access. VMs with endpoints are scheduled only on workers that support endpoint forwarding. Adding endpoints to a VM already assigned to an unsupported worker succeeds, but those endpoints are reported in the `error` state. items: $ref: '#/components/schemas/EndpointSpec' net-softnet: type: boolean description: Please use `netSoftnet` instead default: false deprecated: true netSoftnet: type: boolean description: | Whether to use Softnet network isolation. See `tart run`'s help for `--net-softnet` for more details. default: false netSoftnetAllow: type: array description: | List of CIDRs to allow the traffic to when using Softnet isolation. See `tart run`'s help for `--net-softnet-allow` for more details. Enables `netSoftnet`. example: - "192.168.0.0/24" items: type: string netSoftnetBlock: type: array description: | List of CIDRs to block the traffic to when using Softnet isolation. See `tart run`'s help for `--net-softnet-block` for more details. Enables `netSoftnet`. example: - "66.66.0.0/16" items: type: string suspendable: type: boolean description: | When set, a VM will be started with an additional `--suspendable` command-line argument to `tart run`, which allows suspending it. Further generations of the VM will be `tart suspend`'ed instead of `tart stopped`. For example, this allows you to prepare a VM with loose Softnet settings and then move to the next generation by tightening the settings while preserving the VM's state. default: false net-bridged: type: string description: Whether to use bridged network mode example: en0 headless: type: boolean description: Whether to run without graphics default: false nested: type: boolean description: Enable nested virtualization default: false audio: type: boolean description: | Whether to enable audio pass-through to the host for a Tart VM. Disabled by default. This field cannot be changed after the VM is created. default: false clipboard: type: boolean description: | Whether to enable clipboard sharing between host and guest for a Tart VM. Disabled by default. This field cannot be changed after the VM is created. default: false username: type: string description: SSH username to use when connecting to a VM default: admin password: type: string description: SSH password to use when connecting to a VM default: admin startup_script: type: object description: Startup script to run after the VM boots properties: transport: type: string description: | Transport used to execute the startup script. Omitted or empty values use SSH. `tart-guest-agent` is supported only with the Tart runtime. enum: [ "", ssh, tart-guest-agent ] default: ssh script_content: type: string env: type: object additionalProperties: type: string example: script_content: | #!/bin/zsh echo $GREETING env: GREETING: "Hello, World!" restart_policy: type: string description: | VM restart policy: specify "Never" to never restart or "OnFailure" to only restart when the VM fails default: Never enum: [ Never, OnFailure ] resources: type: object description: Resources required by this VM on the worker additionalProperties: type: integer example: org.cirruslabs.logical-cores: 4 org.cirruslabs.memory-mib: 8192 labels: type: object description: Labels required by this VM on the worker additionalProperties: type: string example: model: macstudio hostDirs: type: array description: | Directories on the Orchard Worker host to mount to a VM. Requires running Orchard Controller with `--insecure-allow-host-dirs`. items: type: object properties: name: type: string path: type: string ro: type: boolean example: - path: /path/on/host/to/sources ro: true - path: /path/on/host/to/builds hostProcesses: type: array description: | Generic long-running processes run by the Orchard worker alongside this VM. Their lifecycle is tied to the VM. Updating this field restarts the associated host processes without restarting the VM. items: $ref: '#/components/schemas/HostProcess' powerState: type: string description: | Desired power state of the VM. When set to `stopped` or `suspended`, the VM does not consume any `resources` and can serve as a source for creating new Orchard VMs on the same worker. See `localName` for more details. Note that you can only transition into `stopped` or `suspended` only once at the moment. default: running enum: [ running, stopped, suspended ] localName: type: string description: | Name of the local VM backing this VM resource. `localName` is specific to a worker, whereas `name` is cluster-wide. `localName` is useful in combination with `powerState` for creating stopped or suspended VMs that can be used to start or resume new VMs on the same worker. However, with great power comes great responsibility. You need to make sure: * that these new VMs will target the same worker using `labels` or `resources`, otherwise they will fail with the "the specified VM does not exist" error * that there's only one cloned new VM for each suspended VM at a time; if you clone more new VMs from a single suspended VM, Tart will give them new MAC addresses automatically, which will stop them from booting, since the suspend‑resume machinery expects the same MAC address readOnly: true tartName: type: string description: | Deprecated alias for `localName`. readOnly: true deprecated: true ConnectionTarget: title: Connection target type: object description: A connection destination relative to the containing resource or action. required: - vm properties: vm: $ref: '#/components/schemas/ConnectionTargetVM' ConnectionTargetVM: title: VM connection target type: object required: - port properties: port: type: integer minimum: 1 maximum: 65535 description: Port inside the contextual VM, using the endpoint's protocol. PortRange: title: Port range type: object description: | Inclusive bounds for selecting a network port. `min` must be less than or equal to `max`; equal bounds identify one port. required: - min - max properties: min: type: integer minimum: 1 maximum: 65535 description: Inclusive lower bound for port selection. max: type: integer minimum: 1 maximum: 65535 description: Inclusive upper bound for port selection. EndpointSpec: title: Endpoint specification type: object description: A desired worker TCP or UDP endpoint backed by a connection target. required: - name - protocol - target properties: name: type: string minLength: 1 description: Stable endpoint identifier, unique within `endpoints`. protocol: type: string enum: [ tcp, udp ] description: | Transport protocol for both the worker socket and the VM target. TCP and UDP endpoints can use the same port number but must have different names. target: $ref: '#/components/schemas/ConnectionTarget' workerPortRange: description: | Optional inclusive bounds for selecting the worker port. When omitted, the operating system selects an available port. Equal bounds request that exact port. allOf: - $ref: '#/components/schemas/PortRange' EndpointStatus: title: Endpoint status type: object description: Current observation for a desired endpoint. required: - name - protocol - state properties: name: type: string minLength: 1 description: Stable endpoint identifier matching the desired endpoint. protocol: type: string enum: [ tcp, udp ] description: Endpoint transport protocol. workerPort: type: integer minimum: 1 maximum: 65535 description: Port assigned to this endpoint on the Orchard Worker for its protocol. state: type: string enum: [ listening, error ] description: | Exposure state. `listening` means that the worker socket is bound; it does not imply that the service inside the VM is healthy or responding. message: type: string description: Human-readable detail, primarily populated when `state` is `error`. HostProcess: title: VM-associated host process type: object description: A long-running process run by the Orchard worker alongside a VM. required: - name - program properties: name: type: string description: Process name, unique within the VM. program: type: string description: Executable path or name resolved using the worker's `PATH`. args: type: array description: | Argument vector passed directly to the configured program without shell interpretation. The worker only expands these placeholders in each argument: - `${ORCHARD_WORKER_NAME}`: name of this Orchard Worker - `${ORCHARD_VM_NAME}`: name of the associated VM - `${ORCHARD_VM_CONTROL_SOCKET}`: path to the VM's control socket - `${ORCHARD_PROCESS_SOCKET}`: Unix socket path that the process must listen on to receive incoming port-forward connections items: type: string env: type: object description: | Additional environment variables passed to the process. `PATH` and the worker-provided `ORCHARD_*` variables take precedence over values supplied here. additionalProperties: type: string VMState: title: Virtual Machine State type: object properties: status: type: string description: VM status enum: [ pending, running, failed ] status_message: type: string description: VM status message worker: type: string description: Worker on which the VM was assigned to observedGeneration: type: number description: Corresponds to the `Generation` value on which the worker had acted upon observedEndpoints: type: array readOnly: true description: | Current observations for the endpoints in `endpoints`. items: $ref: '#/components/schemas/EndpointStatus' Events: title: Events type: object items: $ref: '#/components/schemas/Event' IP: title: Result of VM's IP resolution type: object properties: ip: type: string description: The resolved IP address ExecClientFrame: description: WebSocket frame from Orchard Client to the Orchard Controller oneOf: - $ref: '#/components/schemas/ExecClientFrameStdin' - $ref: '#/components/schemas/ExecClientFrameResize' discriminator: propertyName: type mapping: stdin: '#/components/schemas/ExecClientFrameStdin' resize: '#/components/schemas/ExecClientFrameResize' ExecClientFrameStdin: description: Send bytes to the process standard input type: object required: [ type, data ] properties: type: type: string enum: [ stdin ] data: type: string format: byte description: | Base64-encoded standard input bytes to the process Empty payload indicates EOF and causes standard input to be closed. example: type: stdin data: aGVsbG8K ExecClientFrameResize: description: Resize the pseudo-terminal for a TTY exec session type: object required: [ type, terminal ] properties: type: type: string enum: [ resize ] terminal: $ref: '#/components/schemas/ExecTerminalSize' example: type: resize terminal: rows: 40 cols: 120 ExecControllerFrame: description: WebSocket frame from Orchard Controller to the Orchard Client oneOf: - $ref: '#/components/schemas/ExecControllerFrameStdout' - $ref: '#/components/schemas/ExecControllerFrameStderr' - $ref: '#/components/schemas/ExecControllerFrameExit' - $ref: '#/components/schemas/ExecControllerFrameError' discriminator: propertyName: type mapping: stdout: '#/components/schemas/ExecControllerFrameStdout' stderr: '#/components/schemas/ExecControllerFrameStderr' exit: '#/components/schemas/ExecControllerFrameExit' error: '#/components/schemas/ExecControllerFrameError' ExecControllerFrameStdout: description: Standard output from the process type: object required: [ type, data ] properties: type: type: string enum: [ stdout ] data: type: string format: byte description: Base64-encoded standard output bytes from the process example: type: stdout data: aGVsbG8K ExecControllerFrameStderr: description: Standard error from the process type: object required: [ type, data ] properties: type: type: string enum: [ stderr ] data: type: string format: byte description: Base64-encoded standard error bytes from the process example: type: stderr data: aGVsbG8K ExecControllerFrameExit: description: Process termination details type: object required: [ type, exit ] properties: type: type: string enum: [ exit ] exit: type: object required: [ code ] properties: code: type: integer format: int32 description: Process exit code example: type: exit exit: code: 0 ExecControllerFrameError: description: Error message encountered while running the process type: object required: [ type, error ] properties: type: type: string enum: [ error ] error: type: string description: Error message text example: type: error error: Failed to establish SSH connection to a VM ExecTerminalSize: description: Pseudo-terminal size type: object required: [ rows, cols ] properties: rows: type: integer format: int64 minimum: 0 cols: type: integer format: int64 minimum: 0 Event: title: Generic Resource Event type: object properties: kind: type: string description: Kind of the event payload: type: string description: Payload of the event timestamp: type: integer description: Unix timestamp of the event ServiceAccount: title: Service Account type: object properties: name: type: string description: Name token: type: string description: Secret token used to access the API roles: type: array items: type: string ControllerInfo: title: Controller's Information type: object properties: version: type: string description: Version number commit: type: string description: Commit hash capabilities: type: array items: type: string description: Supported capabilities ClusterSettings: title: Cluster settings type: object properties: hostDirPolicies: type: array description: | If not empty, allows instantiating VMs with `hostDirs` that match the policies listed in this array. items: type: object properties: pathPrefix: type: string ro: type: boolean default: [ ] example: - pathPrefix: /Users/ci/src ro: true schedulerProfile: type: string description: | Scheduler profile to use. Possible values: * `optimize-utilization` — when scheduling a pending VM to a worker, pick the busiest worker that can fit a VM first, falling back to less busier workers (this is the default behavior when no explicit scheduler profile is set) * `distribute-load` — when scheduling a pending VM to a worker, pick the least occupied worker that can fit a VM first, falling back to more busier workers enum: - optimize-utilization - distribute-load default: optimize-utilization