Skip to content

Python SDK

製品(キューブ、サンドボックス、ブラウザ、ドライブ、データベース)ごとの名前空間と、プラットフォーム自体の呼び出しを備えた 1 つのクライアントからなる Python SDK。標準ライブラリのみ、Python 3.9 以降。

更新

インストール

標準ライブラリ以外の依存関係はなく、Python 3.9 以降が必要です。呼び出しはブロックし、いくつのスレッドから同時に呼び出してもかまいません。PyPI のパッケージ名は glac です。

pip install glac

クライアント

from glac import Glac, Sandbox

glac = Glac()                                       # GLAC_API_KEY and GLAC_API_URL; or Glac(api_key=..., base_url=...)

app = glac.cube.deploy("ghcr.io/acme/api:1.4", name="api", port=8080)   # Cube: waits until it serves
print(app.url)

sbx = glac.sandboxes.create(image="python:3.12")    # Sandbox
print(sbx.run("python3 -c 'print(6*7)'").stdout)

drive = glac.drives.create(name="data", size_bytes=10 << 30)               # Drive: disks that outlive sandboxes

print(Sandbox.run("echo hello").stdout)             # the shorthand: a sandbox for one command, on the default client

glac.cube.deploy は glac deploy と同じです。api という名前のアプリ(なければ先に作成)に新しいバージョンを作り、それがアプリのトラフィックを受け取るまで待ちます。最後の行は唯一の短縮形で、サンドボックスの SDK にある、クライアントなしの 1 行の書き方です。with glac.sandboxes.create() as sbx: はブロックの終わりでサンドボックスを削除します。それ以外に何かを削除するのは、使い捨てのサンドボックスと、自分で呼ぶ delete() だけです。

Glac() は、キーを GLAC_API_KEY から、アドレスを GLAC_API_URL(デフォルトは https://api.glac.ai)から読みます。

呼び出し 内容
Glac(api_key=, base_url=, timeout_seconds=, connect_timeout_seconds=, retries=) アドレス、キー、タイムアウト、再試行。1 つのクライアントを、いくつのスレッドで共有してもかまわない
glac.cube キューブ:アプリ(後述)。glac.workers はその別名で、非推奨である旨の警告が出る
glac.sandboxes, .snapshots, .ssh_keys サンドボックス、そのスナップショット、それを開く SSH キー(後述)
glac.drives, glac.volumes, glac.backups ドライブ(コントロールプレーンの機能)、その下にあるボリューム、そしてボリュームのバックアップ(コントロールプレーンの機能。後述)
glac.databases データベース:マネージド Postgres と、psycopg にそのまま渡せる接続文字列(後述)
glac.domains プロジェクトの独自ドメイン。アプリやサンドボックスのポートのための名前(後述)
glac.webhooks, glac.keys プロジェクトのイベントの送信先と API キー(後述)
glac.destinations プロジェクトのアプリのログ、メトリクス、トレースのエクスポート先(後述)
glac.secrets, glac.variables プロジェクトのシークレットと共有変数(後述)
glac.deploys プロジェクトのデプロイ(/v1/deploys、コントロールプレーンの機能):複数のサービスをまとめてデプロイする(後述)
glac.account(), update_account, usage, billing, update_billing, balance, update_balance, top_up(usd), charges, ledger, project, events どの製品にも属さない、プラットフォーム自体の呼び出し(後述)
glac.request("GET", "/v1/webhooks"), glac.pages(path) SDK がラップしていない API。レスポンスの JSON を返す。一覧のすべての項目を、ページを次々にたどって返す

製品は名前空間で、それぞれ、たどってきたクライアントを持っています。オブジェクトには API の名前を使います。キューブは App(API ではアプリ)で、ほかに Sandbox、Snapshot、Drive、DriveSnapshot(ドライブのスナップショット)、Volume、Backup(ボリュームのバックアップ)、Domain、SshKey、Secret、Variable、Version、Instance、Deploy があります。それらに対して行う操作は、それぞれのメソッドです:sbx.run、app.deploy、drive.delete。

短縮形。Sandbox.run、Sandbox.create、Sandbox.get、Sandbox.get_or_create、Sandbox.list は、初めて必要になったときに環境変数から作られ、以降共有されるクライアントでの glac.sandboxes の呼び出しです。client= で別のクライアントを指定できます。コードは同じです。それ以外に、クライアントなしで使えるものはありません。

一部のオプションでは None に意味があります(sleep_after_seconds=None はスリープしない、env={"KEY": None} はそのキーを削除する)。そのため、指定しないオプションは UNSET(from glac import UNSET)で、元のまま残ります。

キューブ

キューブ(API ではアプリ)は、固有のアドレスでサービスとして動くイメージで、インスタンスはリクエストに必要なだけ動き、リクエストがなければ 1 つもありません。デプロイはすべてバージョンになり、準備ができた時点でトラフィックを受け取ります。アプリはコントロールプレーンの機能で、単独で動くノードに対しては、どの呼び出しもノードの 404 no_such_route になります。

呼び出し 内容
glac.cube.deploy(image, name=, ...) イメージをキューブとしてデプロイする。name のアプリに対しては、指定したアプリ自体の設定(regions、scale、metadata、keys、egress、logs、traces、public、depends_on、network)を先に変更し、次にイメージと残りからバージョンを作る。そのアプリがない場合や名前を指定しない場合は、すべてを指定してアプリを作り、最初のバージョンも一緒に作る。その間に誰かが取った名前(409 name_taken)では、その人のアプリへのデプロイになる。バージョンがトラフィックを受け取った時点でアプリを返す
glac.cube.create(name=, image=, port=, env=, secrets=, volumes=, drives=, key_drives=, ..., wait=, timeout_seconds=), get(id_or_name), get_or_create(name, ...), list() アプリを作成する(image を指定すると、最初のバージョンを作って待つ)。1 つを取得する。何もデプロイせずに取得または作成する。一覧。drives は、唯一のインスタンスが読み書き可能でマウントするドライブ(ServiceDrive。多数のインスタンスやキーを持つアプリは 409 drive_single_writer で拒否される)。key_drives は、キーを使うアプリのキーごとのインスタンスに専用のドライブを持たせる(KeyDrive)。ドライブは最初のインスタンスとともに作られ、キーとともに削除される
app.deploy(image=, env=, ...), app.version(n), app.versions(), app.rollback(version) トラフィックを受け取るまで待つ新しいバージョン(Version)。1 つのバージョン。すべてのバージョン。別のバージョンへのトラフィックの切り替え
app.update(...), app.refresh(), app.delete() 変更(バージョンを構成するものなら新しいバージョン)。読み直し。削除
app.managed_by アプリを単独で変更するもの(ManagedBy)。アプリが属するデータベースなら {"object": "database", "id": "db-…"}:ここでの更新、デプロイ、ロールバック、削除は 409 managed_by_database で拒否される(ログの読み取りと削除はほかのアプリと同じ)。サンドボックス製品なら {"object": "sandboxes", "id": None}:プロジェクトのデフォルトのサンドボックスアプリ sbx-sandbox、またはサンドボックスの作成元のイメージのアプリで、そのインスタンスはプロジェクトのサンドボックス。デフォルトのアプリのイメージの指定(app.update(image=))以外の変更はすべて 409 managed_by_sandboxes で拒否され、削除はできる。どれにも属さないアプリでは None
glac.cube.create、deploy、get_or_create、app.deploy、app.update の runtime= バージョンのランタイム設定(ServiceRuntime):ulimits({"nofile": 65535})、sysctls、tmpfs、dns、shm_size_bytes。全体が置き換わる
app.logs(follow=, since=, kinds=, status=, contains=, cursor=, ...), app.log_usage(), app.delete_logs(before=), app.logs_state アプリのログ:オブジェクトストアとマシンからの OutputLine、RequestLine、PlatformLine。接続が切れても再開する。ログが数えた量。行の削除。ログを保持する各マシンが、今そこで出力されるものを長期保存しているかどうか(ログ)
app.instance(key), app.get_instance(key), app.instances(), app.delete_instance(key) キー別インスタンス。キーごとに 1 つで、それぞれサンドボックスである Instance
instance.upgrade(version=, when=, keep_days=), app.upgrade_instance(key, version=, when=, keep_days=); upgrade.wait(timeout_seconds=); instance.upgrades(), app.instance_upgrades(key), app.instance_upgrade(key, id) キーのインスタンスを、アプリの別のバージョンへその場で移す。version で指定しなければアプリの現在のバージョン(ID、番号、または Version。準備ができたもので、古いものも可)。ID、ノード、キー、アドレス、トークン、専用のドライブとその中身はそのままで、ディスク、マシン、プロセスが新しいバージョンのものになり、開いていた接続は終わり、切り替えの間、リクエストはエッジで保留される。Upgrade を返す(status は preparing、switching、done、failed。from_version、to_version、timings、error、put_back。kind(upgrade または revert)、when、keep_days、rollback(リバートに必要なもの。keep_days の間保持される)、reverts、batch。API でインスタンスのスリープを待つよう求めたアップグレードは、スリープするまで waiting で、取り消された場合(API の DELETE、またはバッチの停止)は cancelled。取り消されたアップグレードは終了しており、その待機は失敗したものと同じく、コード cancelled で例外になる)。インスタンスがすでにそのバージョンなら None。wait() は、新しいバージョンが立ち上がらなかった場合に UpgradeError(コードは upgrade_failed か upgrade_not_here)を送出する。そのときインスタンスは元のバージョンのまま。instance.refresh() は、version_id も含めてインスタンスを読み直す。when="asleep" は、インスタンスがスリープするまで待ち(waiting)、スリープした時点で始めるため、リクエストは 1 つも保留されない。keep_days(0〜365 日、指定しなければ 7 日)は、完了後にリバートに必要なものを保持する日数で、スナップショットと同じく課金される。インスタンスとは別のドライブやボリュームをサービスがマウントするバージョンは、その場でのアップグレードにはならず、upgrade_not_here で failed になる
upgrade.cancel(), app.cancel_instance_upgrade(key, upgrade); upgrade.revert(keep_days=), app.revert_instance_upgrade(key, upgrade, keep_days=) 待機中(インスタンスのスリープ待ち、またはバッチ内の順番待ち)のアップグレードを取り消す:cancelled になり、インスタンスは元のバージョンのまま(始まったものは 409 upgrade_started)。リバート:アップグレードが保持していたもの(ディスク、アップグレード前の状態のドライブ(先に現在の中身のスナップショットを作る)、移行元のバージョン)にインスタンスを戻す。kind が revert で、reverts が元のアップグレードを示す新しい Upgrade。アップグレードと同じように進んで待て、今度はそれ自体のリバートに必要なものを保持する。戻り先がもう保持されていなければ 409 rollback_gone で拒否される
app.upgrade_instances(version=, from_versions=, keys=, when=, keep_days=, concurrency=, max_failures=); app.upgrade_batches(), app.upgrade_batch(id), app.stop_upgrade_batch(id); batch.wait(timeout_seconds=), batch.refresh(), batch.stop() アプリの複数のキーを 1 つのバージョン(指定しなければアプリの現在のバージョン)へまとめてアップグレードするバッチ。指定すれば from_versions のもの、keys に含まれるものが対象。それぞれインスタンスがスリープした時点で(when="asleep"。デフォルト)、または順番が来た時点で("now")、同時に concurrency 個ずつ(4。1 つのノードでは 2)行い、max_failures(5)回失敗した時点で failed として止まる。UpgradeBatch を返す:status(running、done、failed、stopped)、stop_reason、counts(total、waiting、preparing、switching、done、failed、cancelled)、upgrades(各メンバーのキー、アップグレード、ステータス、エラー)、skipped(終わっていない独自のアップグレードを持つキー)、origin(api、またはアプリの keys が upgrade_on_deploy を指定している場合の deploy)。wait() はどう終わってもそれを返し、例外は送出しない。スリープしないインスタンスのメンバーがあるとバッチは running のままなので、timeout_seconds で GlacTimeoutError が送出される。stop() は待機中のメンバーを取り消す
app.open_instance_port(key, port, from_=[...]), app.instance_ports(key), app.close_instance_port(key, port) インスタンスがどこにあっても、キーのインスタンスが持つ専用のパブリック IPv6 アドレスで開いているポート:そのサンドボックスの open_port、ports、close_port を、アプリとキーで指定(UDP なら protocol="udp"。キー別インスタンス)。専用のアドレスのないインスタンスは拒否される(409 no_ipv6)
glac.cube.create、deploy、get_or_create、app.update の checkpoint=。app.checkpoint。sbx.checkpoint アプリのキー別インスタンスの稼働中のチェックポイント(AppCheckpoint、{"every_seconds": n}:1〜600 秒。省略するか None なら 10 秒。app.update(checkpoint=None) で解除)と、インスタンスのチェックポイントの状況(CheckpointStatus:every_seconds、last_at、behind_seconds、bytes_per_second、stored_bytes、error。なければ None)
app.metrics(start=, end=, step=) アプリを処理するマシンが計測した値。1 分ごと(step="hour" なら 1 時間ごと):ステータスのクラス別のリクエスト、所要時間、インスタンスとその使用量(メトリクス)。API のオブジェクトを返す
glac.cube.create、deploy、get_or_create、app.update の traces=。app.traces アプリのリクエストの一定割合について作られるプラットフォームのスパンで、インスタンス自身のスパンはその子になる(AppTraceSettings:{"sample_rate": 0.1, "trust_incoming": False}。指定しなければなし)。"traces" を受け付けるプロジェクトのエクスポート先に送られる(トレース)。アプリの設定
app.create_key_secret(), app.key_secrets(), app.key_secret(id), app.delete_key_secret(id), glac.sign_key(secret, app_id, key, expires_at=, path=) アプリのキーの署名に使うシークレット(KeySecret。secret を持つのは作成直後のものだけ)と、それで署名した、エッジを通るリクエストが運ぶ形のキー(署名付きキー)。path=True で、パスのセグメントとして書く
from glac import DeployError, Glac

glac = Glac()
app = glac.cube.deploy("public.ecr.aws/docker/library/python:3.12-alpine", name="hello",
                          start_command=["python3", "-m", "http.server", "8000"], port=8000)   # waits for version 1
print(app.url)
app.deploy(env={"STAGE": "prod"})               # a new version; waits until it has the traffic
try:
    app.deploy(start_command="exit 3")
except DeployError as e:                        # the traffic stays where it was
    print(e.code, e.version.number)
app.rollback(1)
for line in app.logs(follow=False):
    print(line)

バージョンをビルドする呼び出しは、wait=False を指定しない限りそれを待ち、失敗すると、バージョン自身のコードと出力を持つ DeployError を送出します。timeout_seconds= を指定すると、その時間が過ぎた時点で GlacTimeoutError を送出し、デプロイはそのまま続きます。

glac.deploys で、プロジェクトの複数のサービスをまとめてデプロイします(プロジェクトのデプロイ)。コントロールプレーンは、それぞれが待つものの順に進め(ステートフルなサービス、ジョブ、残りの順で、残りはまとめて切り替え)、失敗したデプロイは、まだ切り替わっていないものを何も切り替えません。

d = glac.deploys.create({"api": {"image": "ghcr.io/acme/api:2"}, "web": {}})   # waits until it is over
print(d.status, [(s.service, s.status, s.version) for s in d.services])

create(services, wait=True, timeout_seconds=None) は、各サービスのバージョンのフィールドを API と同じ形で受け取り({} ならそのまま)、待ちます(Deploy.wait)。置き換わったデプロイがあればそれを追います。失敗すると、deploy、service、デプロイの code(job_failed、job_timed_out、job_lost など)を持つ DeployError を送出します。get(id) と list()(新しい順)で読み取ります。Deploy は status、cause、services(DeployService)、error、replaced_by、steps と、各時刻を持ちます。

サンドボックス

呼び出し 内容
glac.sandboxes.create(image=, snapshot=, name=, env=, metadata=, volumes=, drives=, public_ports=, sleep_after_seconds=, sleep_notice_ms=, sleep_hold_max_ms=, expires_at=, egress=, start=, start_command=, ready=, project_network=, hostname=, stop=, checkpoint=, image_pull_secret=) サンドボックスを作成する。どのオプションも省略可能。drives=[DriveMount("/data", drive)] でドライブをマウントする(後述)。起動時に始めるサービス:start=True(イメージ自身の起動コマンド)か start_command= を、ready={"tcp": True, "port": 5432}(または {"http": "/health", "port": 8080}、{"command": "…"})とともに指定。hostname= でマシンに名前を付け、stop={"signal": "SIGINT", "grace_ms": 30000} でサービスの止め方を指定する。project_network=True で最初からプロジェクトのプライベートネットワークの sbx.project_address につなぐ。VolumeMount("/data", vol, copy=True) は、イメージのそのパスにあるものを新しいボリュームに入れる(サンドボックスの作成)
sbx.app_id, sbx.version_id, sbx.key, sbx.image_digest コントロールプレーンで作ったサンドボックスはアプリのインスタンスである。イメージを指定せずに作った場合はプロジェクトのデフォルトのサンドボックスアプリ(sbx-sandbox)、それ以外はそのイメージのアプリ(glac.apps.get(sbx.app_id).instances() は、そのアプリのサンドボックスをそれぞれのキーの下に一覧表示する)。サンドボックスが属するそのアプリのバージョン、作成元のイメージ(その後移動したタグはアプリの新しいバージョンになり、それ以降に作ったサンドボックスはそちらに属する)、そこでのキー、名前または ID、ダイジェストで示すイメージ。作成のレスポンスとすべての読み取りで示され、保持される。テンプレートやスナップショットから作ったものと、フォークでは None
glac.sandboxes.get(id_or_name), get_or_create(name, ...), list(status=, metadata=) 1 つを取得。取得または作成。一覧
sbx.start_command ゲストが最後に報告した、起動コマンドの実行状況(StartCommandState):status(running、restarting_at に再起動した restarting、exited)、restarts(キーのインスタンスとステートフルなサービスのインスタンスは、コマンドが自ら終わるとマシン内で再起動され、起動のたびにイベント instance.restarted が発生する)、last_exit(status、signal、at)。ノードが何も報告していなければ None。Instance も持つ
create、get_or_create、sbx.update の suspend_after_ms=、wake_daily_at=。sbx.suspend_after_ms、sbx.wake_daily_at アイドルで停止した後、メモリを保存して本当にスリープするまで一時停止したままでいる時間(1 秒〜1 日。ノードは最大 10 分に抑える)と、休止中に、復帰を待つ自身の処理のために毎日復帰させる時刻(UTC、"HH:MM")。更新で None を指定すると、それぞれ元に戻る
create、get_or_create、sbx.update の on_expiry=、held_asleep=。sbx.on_expiry、sbx.held_asleep expires_at の時刻が来たときの動作:サンドボックスを "delete"(デフォルト)、"sleep"(スリープさせて保持し、リクエストではいつもどおり復帰)、"hold"(同じく、スリープのまま保持)。そしてスリープ中にポートへのリクエストで復帰させるかどうか。スリープのまま保持されている間、プレビューアドレスは 502 sandbox_paused を返し、API の呼び出しでは復帰する。更新で None を指定すると、それぞれ元に戻る
create、get_or_create、run の init="systemd"。sbx.init システム全体:イメージ自身の systemd がサンドボックスの PID 1 になる(image を指定しなければ、systemd と OpenSSH 入りのプラットフォームの Ubuntu)。パッケージ、systemctl で起動するサービス、cron、ジャーナルが通常のマシンと同じように動き、専用のマシン ID を持ち、スリープ、復帰、移動を経ても保たれる。start、start_command、ready とは併用できず(400 invalid_start)、snapshot とも併用できない。sbx.init は、systemd なら "systemd"、プラットフォームの init なら None
glac.sandboxes.run(command, ...) 1 つのコマンドのためのサンドボックス:作成、利用、削除
sbx.run(command, cwd=, env=, user=, timeout_seconds=, stdin=, on_stdout=, on_stderr=) 実行して、どれだけかかっても待つ。Result を返す
sbx.exec(command, stdin_open=, tty=) 開始して、すぐに Command を返す:wait()、output()、write()、kill()
sbx.terminal(command=, cols=, rows=, join=) ターミナル
sbx.files read、read_text、write、list、stat、exists、mkdir、move、remove、そして set_attributes(path, mode=, size_bytes=, modified_at=):ファイルのモード、長さ、最終更新時刻
sbx.files.upload(local_path, path), download(path, local_path), read_stream(path, offset=, length=, chunk_size=), write(path, file_or_iterable) どんな長さのファイルも 1 回の呼び出しで扱い、こちらでも API でも、ファイル全体をどこかに保持することはない。upload はローカルファイルを読みながら送り、download は届くそばからバイトをローカルファイルに書き込み(すべてのバイトが届いた時点で所定の場所に置く)、read_stream は少しずつバイトを渡す。write は、バイナリで開いたファイル(open(p, "rb"))やバイトの任意のイテラブルも受け付ける。長さに満たないまま終わったレスポンスは GlacConnectionError を送出するので、読み直す
sbx.url(port), sbx.rotate_urls(), sbx.token(scopes=, expires_in_seconds=) ポートへのリンク(ローカルで署名し、リクエストは不要)。リンクの新しいシークレット。このサンドボックスだけのためのトークン
sbx.ipv6, sbx.open_port(port, from_=[…], protocol="tcp"), sbx.ports("tcp"), sbx.close_port(port, protocol="tcp"), sbx.update(inbound=[…]) 専用のパブリック IPv6 アドレス(ある場合。なければ None)。指定した送信元(IPv6 のプレフィックスかアドレス。全員なら "::/0")に対して開いたポートと、その接続先。開いているポートの一覧。ポートを閉じる(開いていたかどうかを返す)。一覧全体(それぞれ protocol、port、from の辞書)。受信はデフォルトで閉じており、開いたものはスリープ中も移動しても開いたまま。ネットワークを参照
sbx.revoke_token(token) そのトークンだけがそれ以降拒否され、ほかのトークンはそれまでどおり使える。Token(その id である tok_… で特定される)、トークンそのもの、またはその ID で指定。1 つのサンドボックスで保持できる、期限の切れていない個別の無効化は 1,000 個まで(超えると 409 too_many_revoked_tokens。その場合は revoke_tokens() を使う)。token_id と expires_at(トークンの期限で、その無効化もそこで終わる)を持つ TokenRevocation を返す
sbx.revoke_tokens() それまでにこのサンドボックス用に発行したトークンがすべて使えなくなる(401 invalid_token)。プレビューリンクや Cookie の中のトークン、SSH セッションが持つトークンも含む。その後に作ったトークンは使える。API キーが必要。TokenRevocation(sandbox_id、generation、revoked_at)を返す
sbx.update(...), sbx.refresh(), sbx.sleep(), sbx.restart(), sbx.delete() 変更。読み直し。すぐにスリープ。ディスクからの起動。削除
glac.sandboxes.create と sbx.update の checkpoint=。sbx.checkpoint 稼働中のチェックポイント({"every_seconds": 10}。sbx.update(checkpoint=None) で解除)。マシンが失われてもプロセスごと再開できる。状況(CheckpointStatus)。チェックポイントを取れるマシンがなければ 409 checkpoints_unavailable
sbx.disk_writes 今日(UTC の 1 日)のディスクの書き込み量と、全速で書き込める 1 日の割り当て量(SandboxDiskWrites):today_bytes(うちチェックポイントのコピーがホストのディスクに書き込んだ量が checkpoint_copy_bytes)、full_speed_until_bytes(割り当て量。ノードに別の指定がなければ 100 GiB。None ならノードは何も減速しないが、数えることは同じ)、limited と limited_until(割り当てを超えると、その日が終わるまでディスクは毎秒 2 MiB で書き込みを受け付け、失敗する書き込みはない)。ディスクをオブジェクトストアにコピーしないノードでは None で、何も数えない。ドライブへの書き込みはここでは数えない(ドライブへの書き込みに料金はかからない)
sbx.snapshot(name=), sbx.fork(count), sbx.restore(snapshot) スナップショット。フォーク。その場でのスナップショットへの復元
glac.sandboxes.snapshots.get(id_or_name), .list(sandbox_id=), snap.update(name=, expires_at=) スナップショットと、それぞれがもうオブジェクトストアにあるかどうか(replicated)。別の名前(None ならなし)、または自動で削除される時刻(None なら削除しない)
glac.sandboxes.ssh_keys.create(public_key, sandbox_id=, expires_at=), .get, .list SSH ゲートウェイを通じてサンドボックスを開く公開鍵。秘密鍵は、何かを送る前に拒否される
sbx.run_code(code), sbx.agent("claude"), Desktop(sbx) コードインタープリター、エージェント、デスクトップのイメージから作ったサンドボックスでの、状態を保つコード実行、コーディングエージェント、デスクトップの画面

glac.sandboxes.run(とクラスの Sandbox.run)は 1 つのコマンドのためのサンドボックスで、sbx.run はサンドボックス sbx でコマンドを実行します。出力を逐次受け取るには次のようにします。

res = sbx.run("make", on_stdout=lambda text: print(text, end=""))   # watch and wait

cmd = sbx.exec("node server.js")                                    # do not wait
for piece in cmd.output():                                          # piece.stream, .text, .data, .offset
    print(piece.text, end="")

出力は、コマンドが書いたとおりのバイトで届きます。res.stdout は str(UTF-8。デコードできないバイトは置き換え)、res.stdout_raw は bytes です。

ドライブ

ドライブは、サンドボックスより長く残るプロジェクトのディスクです。サンドボックスの作成時か実行中に、一度に 1 つのサンドボックスが読み書き可能でマウントし、サンドボックスがなくなると中身ごと保持され、次のサンドボックスがマウントできます。次のサンドボックスは同じリージョンのもの、またはマシンがホームのオブジェクトストアを読める別のリージョンのもの(後述)です。書き込まれるそばからリージョンのオブジェクトストアにコピーされるため、別のノード上のサンドボックスもマウントできます。その場合は先にそこへ運ばれます。課金されるのは書き込まれたものだけです(used_bytes)。スナップショットはオブジェクトストア内のドライブのバージョンで、いくつものサンドボックスが同時に読み取り専用でマウントでき、そこから新しいドライブをフォークしたり、ドライブをその状態に復元したりできます(後述)。ドライブはコントロールプレーンの機能で(/v1/drives)、単独で動くノードに対しては、どの呼び出しもノードの 404 no_such_route になり、そのノードのディスクはボリュームです(後述)。

呼び出し 内容
glac.drives.create(name=, size_bytes=, region=, durability=), get(id_or_name), list() ファイルシステム以外は空のドライブを作る。どのオプションも省略可能(size_bytes:4 KiB ブロックの整数倍で最小 16 MiB。指定しなければ 64 GiB、無料プランでは 5 GiB。region:na、eu、ap。durability:standard、またはゲストの fsync が、それ以前のすべての書き込みがオブジェクトストアに届いてから戻る strict)。1 つを取得。新しい順の一覧
glac.drives.update(drive, name=), glac.drives.delete(drive) 名前の変更(None なら名前なし)。中身ごと削除
glac.drives.attach(sandbox, drive, path, mode=), glac.drives.detach(sandbox, drive, force=) 既存のサンドボックスに、読み書き可能ですぐにマウントする(スリープ中のサンドボックスは復帰する)。アンマウントする(プロセスが使っている間は force=True)。どれもドライブを返す
drive.attach(sandbox, path), drive.detach(sandbox), drive.update(name=), drive.refresh(), drive.delete(); drive.snapshot(name=, fs_consistent=), drive.snapshots(), drive.fork(name=, from_snapshot=), drive.restore(snapshot) 同じことを Drive に対して行う(fork は新しいドライブを、restore はドライブ自体を返す):id(vol-…。ドライブはボリュームでもある)、name、size_bytes、used_bytes、attached_to(DriveAttachment:サンドボックス、パス、モード。スナップショットの読み取り専用のマウントは含まない)、region、home、status、lease(DriveLease:唯一の書き込み元のノード、エポック、期限)、app_id と key(キー別インスタンスのもの)、parent_drive_id と root_drive_id(フォーク元のドライブと系統の最初のドライブ。その後削除されている場合もある。フォークでないドライブでは None)、managed_by(ManagedBy。データベースのドライブのもの。それ以外では None)、created_at
glac.drives.snapshot(drive, name=, fs_consistent=), snapshots(drive), all_snapshots(drive_deleted=), get_snapshot(drive, snapshot), delete_snapshot(drive, snapshot) スナップショットを取る(DriveSnapshot)。ドライブのスナップショットを新しい順に、またはプロジェクトの全ドライブのスナップショットを一覧表示する(drive_deleted=True ならその後削除されたドライブのものだけ)。1 つを取得する。1 つを削除する。drive は Drive、ID、または名前(削除済みのドライブは ID)、snapshot は DriveSnapshot か、その ID(dsnap-…)
glac.drives.fork(drive, name=, from_snapshot=), glac.drives.restore(drive, snapshot) スナップショット、またはドライブの現在の状態を最初のバージョンとする新しいドライブ:新しいドライブを返す。ドライブをスナップショットの 1 つに戻す:ドライブを返す
glac.drives.create(..., history_seconds=), update(drive, history_seconds=); glac.drives.versions(drive, at=), fork(drive, at=), restore(drive, at=); drive.versions(at=), drive.history_seconds, drive.history ドライブのバージョンのウィンドウ(slice DB8):時刻を指定して、どこまで遡って復元またはフォークできるか。3600〜86400 秒(指定しなければなし。オフにすると、保持していたものは消える)。書き込みがあった 30 秒ごとに、ある時点のドライブであるバージョンが 1 つウィンドウに加わり、それらが単独で保持するものは、スナップショットと同じく課金される。時刻を指定した復元やフォークでは、その時刻以前の最新のバージョンが使われる。その時刻を指定してバージョンの一覧を呼び出すと、そのバージョンが示される。versions は DriveVersion(at、version、consistency、exclusive_bytes)を新しい順に返す。restore はスナップショットか at のどちらか一方を受け付ける(両方、またはどちらもない場合はここで ValueError)。update(history_seconds=None) でウィンドウをオフにする
glac.drives.create(..., cache_bytes=), update(drive, cache_bytes=); drive.update(cache_bytes=), drive.cache_bytes, drive.cached_bytes ドライブのキャッシュ:ノードが NVMe に保持するドライブの最大量。指定しなければ全量(ドライブのサイズ)。0 まで減らせ、それを超えた分はオブジェクトストアだけにあり、ゲストが読むとそこから読み取られる(最も長く読まれていないものから手放し、ストアにまだないものは手放さない)。キャッシュは、ドライブが保持するデータとは別に、埋まっている量に応じて 1 時間ごとに課金される。小さくすると、超えた分はストアが保持した時点で手放される(実行中のサンドボックスが全量キャッシュでマウントしたドライブでは、そのサンドボックスが次にスリープまたは停止した時点から)。大きくすると、それ以降にゲストが読んだものを保持する。API ではドライブをボリューム(/v1/volumes)と呼び、クライアントは /v1/drives でアクセスする(以前と同じように応答する)。更新で cache_bytes=None を指定すると、再び全量になる。コントロールプレーンがキャッシュについて何も示さない場合、drive.cache_bytes はドライブのサイズ。drive.cached_bytes は、現在ノードの NVMe にある量(どのノードも保持していなければ None)
glac.drives.attach_snapshot(sandbox, snapshot, path, drive=), glac.drives.detach_snapshot(sandbox, snapshot, force=) 既存のサンドボックスに、スナップショットを読み取り専用ですぐにマウントする。マウントを外す。どちらもスナップショットを返す。ID で指定したスナップショットには drive= が必要(DriveSnapshot はドライブを持っている)。detach_snapshot は、同じパスでドライブの読み書き可能なマウントも外れるため、スナップショットの ID 以外を送る前に拒否する(ValueError)
snap.refresh(), snap.fork(name=), snap.restore(), snap.attach(sandbox, path), snap.detach(sandbox, force=), snap.delete() 同じことを DriveSnapshot に対して行う:id、drive_id(そのドライブ。その後削除されている場合もある)、name、consistency(frozen、paused、at_rest)、used_bytes(取得時にドライブが保持していた量)、exclusive_bytes(単独で保持する量で、課金の基準)、exclusive_at、version(DriveSnapshotVersion:number、epoch)、created_at
DriveMount(path, drive, mode=None, snapshot=None) glac.sandboxes.create(drives=[...]) と get_or_create のためのドライブ:ボリュームと合わせて最大 8 個。snapshot(DriveSnapshot か、その ID)を指定すると、そのスナップショットを読み取り専用でマウントする。mode: "snapshot" とスナップショットの ID を送る
from glac import DriveMount

drive = glac.drives.create(name="data", size_bytes=10 << 30)
sbx = glac.sandboxes.create(drives=[DriveMount("/data", drive)])
sbx.run("echo kept > /data/note")
glac.drives.detach(sbx, drive)
other = glac.sandboxes.create()
glac.drives.attach(other, drive, "/data")
print(other.run("cat /data/note").stdout)      # kept
  • ドライブは一度に 1 つのサンドボックスのものです。ほかのサンドボックスが持っているドライブは拒否され(409 drive_attached)、アタッチ中のドライブの削除も拒否されます。キー別インスタンスのドライブは、キーとともに削除されます(409 key_drive)。
  • データベースのドライブ(drive.managed_by、{"object": "database", "id": "db-…"})を読み書き可能でマウントできるのはデータベースのインスタンスだけで、名前の変更、復元、削除はデータベースを通じて行います。ここでのそれらの呼び出しと、読み書き可能なマウントは拒否されます(409 managed_by_database)。そのスナップショットとフォークはプロジェクトで取得でき、スナップショットは誰でも読み取り専用でマウントできます。
  • 別のノードが保持しているドライブは、先にサンドボックスのノードへ運ばれます。オブジェクトストアがまだ現在の状態を保持していない間(503 copy_behind)や、運べなかった場合(503 drive_not_brought)、呼び出しは retryable です。クライアントは retries の指定どおりに再試行し、それでも失敗すれば例外を送出するので、少し後に再度要求してください。
  • リージョンをまたぐ場合(slice D9)。ドライブのホーム(home)とは、そのコピーを保持するオブジェクトストアのリージョンで、作成されたリージョンです。別のリージョンのサンドボックスも、そのリージョンのマシンがホームのストアを読める場合はマウントできます(読めなければ 409 not_on_one_machine)。ドライブはバージョンとして手放され、そこで引き継がれ、ゲストが読むのに合わせてホームのストアから読み取られ、ゲストが書き込むものは今いるリージョンのストアに送られます(region)。ホームは、そこに 1 時間いると追従します。glac.drives.move_home(drive)(drive.move_home())を使えばすぐに移ります。マシンがホームのストアに残っているものを自分のストアにコピーし、何も残らなくなった時点で home が新しいリージョンになります。drive.home_move が移動の進み具合を示します(asked_at、asked_by、copied_objects、copied_bytes、objects_left、error)。古いホームのコピーは、保持しているどのバージョンも必要としなくなった時点で消えます(1 日後)。ドライブのホームが移るのは最大で 6 時間に 1 回で(409 home_moved_recently)、ホームが現在のリージョンにあるドライブでは 409 home_here になります。
  • プランの制限:無料プランでは最大 10 GiB のドライブを 3 つ(合計 15 GiB)、有料プランでは最大 1 TiB のドライブを 500 個です(403 drive_limit_reached)。
  • glac.usage(drive=...)、kind="drive"、group_by="drive" は、ドライブが保持した量、書き込まれた量、キャッシュ外から読まれた量を報告します。イベント drive.created、drive.deleted、drive.attached、drive.detached は EventDrive か EventDriveAttached を、drive.snapshot.created と drive.snapshot.deleted は EventDriveSnapshot を運びます。
  • glac.drives.attach の mode は read_write です。スナップショットの読み取り専用のマウントは attach_snapshot で行い、これはスナップショットを返します(スナップショットなしの mode="snapshot" は 400 missing_field)。

**ドライブのスナップショット。**スナップショットはオブジェクトストア内のドライブのバージョンで、そのためにも、フォーク、復元、読み取り専用のマウントのためにも、何もコピーされません。

snap = drive.snapshot(name="before-upgrade")             # a running sandbox has the Drive: its writes held a moment, "paused"
reader = glac.sandboxes.create(drives=[DriveMount("/before", drive, snapshot=snap)])   # read-only, any number of sandboxes
copy = glac.drives.fork(drive, name="data-copy", from_snapshot=snap)
glac.drives.detach(sbx, drive)
glac.drives.restore(drive, snap)                         # once no sandbox has it read-write
  • 実行中のサンドボックスが持っているドライブは、書き込みを短時間(最大 10 秒。超えると再試行可能な 503 unavailable)止めてスナップショットを取ります。fs_consistent=True なら先にファイルシステムを凍結し(consistency は frozen。サンドボックスのエージェントが凍結できなければ 409 not_frozen)、そうでなければ、停電時と同じようにマシンを一時停止します(paused)。実行中のサンドボックスが持っていないドライブは、そのまま取ります(at_rest)。
  • スナップショットはドライブより長く残り、ドライブはスナップショットを残して削除されます。ドライブの削除後も、スナップショット自体が削除されるまで(snap.delete())、ドライブの ID で一覧表示、読み取り、フォーク、マウントができます。単独で保持する量(exclusive_bytes)に対して、1 時間ごとに、ドライブのデータの料金(1 GB-月あたり $0.02)で課金され、glac.usage(snapshot=snap.id) がそれを報告します。
  • フォークはすぐにマウントできる新しいドライブで、サイズは元のドライブと同じで、作成元を示します(parent_drive_id、root_drive_id)。プランのドライブ数に数えられ(403 drive_limit_reached)、プロジェクトにすでにある名前は 409 name_taken になります。ほかのドライブと同じく、used_bytes、つまり内容全体で課金されます。10 GB のスナップショットのフォークは、10 GB のドライブとして課金されます。
  • 復元は、ドライブの最新のバージョンの後に、スナップショットの内容を持つ新しいバージョンを書き込みます。その間のバージョンは、スナップショットかフォークが保持している限り残るため、古いスナップショットに復元した後に新しいスナップショットに復元することもできます。サンドボックスがドライブを読み書き可能で持っている間は拒否されます(409 drive_attached。先にデタッチしてください)。
  • 読み取り専用のマウント:ドライブのリージョンのどのノード上でも、ドライブを誰が持っていても、ドライブが削除された後でも、いくつものサンドボックスが同時にマウントでき、ゲストは書き込めません。ブロックはゲストが読むのに合わせてオブジェクトストアから届きます。このキャッシュ外の読み取りに料金はかからず、使用量ではそのサンドボックスの下に表示されます(glac.usage(sandbox=...)、drive_cold_read_bytes)。そのサンドボックスのマシンがすでに保持しているものの読み取りにも料金はかかりません。読み取り専用のマウントを持つサンドボックスは、別のマシンへ移動しません。
  • ドライブのノードがオブジェクトストアに何もコピーしない場合、スナップショット、フォーク、復元はありません(409 not_copied)。アプリのバージョンはドライブを読み書き可能でしかマウントせず(そこでの mode snapshot は 400 invalid_field)、スナップショットの読み取り専用のマウントはサンドボックス用です。

glac.volumes は API の /v1/volumes で、ドライブの下にあるディスクです。移行のために残されています。これにしかないのは、ディスクがボリュームである単独で動くノードと、複数のサンドボックスが同時にマウントするファイルシステムである共有ボリュームです。コントロールプレーンは、プロジェクトのキーに対して通常のボリュームを作りません。

from glac import VolumeMount

vol = glac.volumes.create(name="team", shared=True)     # no size: a large limit, and the space of what is written
sbx = glac.sandboxes.create(volumes=[VolumeMount("/team", vol)])
print([v.name for v in glac.volumes.list()], glac.volumes.get("team").id)

マウント中のボリュームは削除されません(409 conflict)。sbx.update(volumes=[]) で外してください。

**ボリュームのバックアップ。**バックアップは、ある瞬間のボリューム(ドライブもボリュームです)のコピーで、オブジェクトストアに、期限が来るまで保持されます。呼び出しによって、ボリュームのルールに従って毎日、またはサービスの新しいバージョンがインスタンスを置き換える前に取得されます。ドライブのスナップショットと違い、それ自体が独立したコピーで、それだけからボリュームを作ることができ、元のボリュームより長く残ります。ボリュームのバックアップは前回以降に変わったものだけを保持し、複数のバックアップが必要とするブロックは、そのうち最も古いもので 1 回だけ課金されます。ストアがすべてを受け取るまでは making、その後は ready で、それ以降はストアが保持する量(stored_bytes)に応じて、スリープ中のサンドボックスが保持するものと同じ料金で課金されます(バックアップ)。バックアップはコントロールプレーンの機能です。

import time

b = glac.volumes.backup("data", expires_in_days=7)      # "making", then "ready"
while b.refresh().status == "making":
    time.sleep(1)
copy = glac.backups.restore(b, name="data-back")        # a new volume: "restoring", then "ready"
呼び出し 内容
glac.volumes.backup(volume, expires_in_days=), vol.backup(expires_in_days=), glac.volumes.backups(volume) 今すぐバックアップを取り、Backup を返す。expires_in_days(1〜365 日)、またはボリュームのルールの日数(設定しなければ 30 日)保持される。ボリュームのバックアップの一覧(新しい順。削除済みのボリュームのものは、その ID で)。オブジェクトストアに何もコピーしないノードにはバックアップはない(409 not_copied)
glac.backups.list(volume=), get(backup), delete(backup), restore(backup, name=) プロジェクトのバックアップの一覧(新しい順。削除済みのボリュームのものも含む)、または 1 つのボリュームのもの。1 つの詳細。1 つの削除(それ以降は課金されず、そこから作ったボリュームは残る)。準備ができたバックアップから作る新しいボリューム。glac.volumes.create(from_backup=, name=) と同じ(作成中は 409 backup_not_ready、失敗後は 409 backup_unusable)
b.refresh(), b.restore(name=), b.delete() 同じことを Backup に対して行う:id(bak-…)、volume_id、volume_name(その時点のボリュームの名前)、status(making、ready、failed)、reason(manual、daily、before_version)、consistency(frozen、crash)、frozen_ms(ボリュームの書き込み元がコピーのために待った時間。凍結しなかった場合は None)、size_bytes(ボリュームが保持していた量)、volume_size_bytes(保持できた最大量。そこから作るボリュームが保持する量)、stored_bytes(課金の基準)、own_bytes(削除すると減る量)、shared_bytes、shared、region、failure、created_at、ready_at(課金の開始時刻)、expires_at
  • バックアップから作るボリュームは、バックアップ元のボリュームと同じサイズと種類で(from_backup では size_bytes と shared を指定しません:ValueError)、バックアップのリージョンに作られます。status は、バックアップ全体を保持するまでは restoring、その後は ready で、それまではアタッチできません。from_backup がバックアップを示します。配置先のマシンで復元できない場合、コントロールプレーンはリージョンの別のマシンにもう一度作ります。そのとき vol.info["restore_attempt"] は 2 になり、vol.info["first_failure"] が最初の試行に失敗した理由を示します。それも失敗すると failed(vol.info["failure"])になり、削除しかできません。
  • glac.usage(backup=...)、kind="backup"、group_by="backup" は、バックアップが保持した量を、状態の計測項目(state_gb_hours)で報告します。glac.billing()["meters"]["state"]["backups_gb_hours"] は、その計測項目のうちバックアップの分を示します。

ドメイン

プロジェクトのドメインは、デフォルトのアドレスと同じくアプリへ、またはサンドボックスのポートへ向かう独自の名前です(独自ドメイン)。ドメインはコントロールプレーンの機能です。

呼び出し 内容
glac.domains.create(domain, app=) または (domain, sandbox=, port=)(proxied= を指定可能) 名前を登録する。domain.verification["records"] のレコード(名前をここへ向ける役割も兼ねる CNAME か、TXT レコード)が DNS に現れるまでは pending_verification。プロジェクトにすでにあれば 409 domain_exists
glac.domains.get(id_or_name), list(app=, sandbox=), delete(domain) 1 つを取得。一覧、またはアプリかサンドボックスへ向かうものの一覧。1 つを削除
domain.update(app=, sandbox=, port=, proxied=), domain.refresh(), domain.delete() 向け先を変更。読み直し(status、certificate)。削除
domain = glac.domains.create("api.example.com", app="api")
print(domain.verification["records"])

ブラウザ

**未公開。**Glac のブラウザはまだ利用できません。以下は公開に先立つ、現時点での SDK です。

セッションは Glac がホストする Chromium で、Playwright、Puppeteer など任意の Chrome DevTools Protocol(CDP)クライアントから外部で操作します。クライアントの接続中は稼働し、最後のクライアントが離れて sleep_after_seconds が過ぎると、ページを保持したままスリープします。CDP クライアントに必要なのは cdp_url だけです。認証情報はアドレスのホスト名に含まれるため、repr には表示されません。

呼び出し 内容
glac.browsers.create(kind=, name=, region=, sleep_after_seconds=, connection_idle_seconds=, delete_after_idle_seconds=, metadata=, profile=, proxy=) 稼働中の新しいセッション(kind:デフォルトの chromium-headless、画面付きの chromium、またはレイアウトエンジンのないブラウザである lightpanda)。既存のセッションの名前を指定すると、そのセッションがページとともに復帰して返される。profile:プロファイルの名前、または {"name", "save", "version"}。セッションを返す前に読み込む(後述)。proxy:自前のプロキシの URL(後述)
glac.browsers.get(id_or_name), list(status=, kind=, name=) 1 つを取得。一覧(新しい順。status:awake、asleep、unavailable、failed)。実行前にサンドボックスが失敗したものはセッションにならず、作成は 503 session_failed で拒否される
session.update(...), session.sleep(), session.rotate_url(), session.delete(save_profile=None) 時間の設定、メタデータ、プロキシの変更。すぐにスリープさせる(開いている接続は残り、次のメッセージで復帰する)。新しいアドレスを発行(古いアドレスは拒否され、それで入った接続は切断される)。終了(保存するプロファイルを指定して作ったセッションは、save_profile=False でない限り、先にプロファイルを保存)。glac.browsers は、どれについてもセッション、ID、名前を受け付ける
session.cdp_url CDP クライアントの接続先:Playwright の connect_over_cdp、Puppeteer の browserURL
session.status, session.error awake、asleep、unavailable(ノードに到達できない)、failed(作成に応答した後でサンドボックスが失敗した。たとえばブラウザが停止して戻らなかった)。error は理由をノードが返したとおりの {"code", "message"} で示し、失敗していなければ None。イベント browser.failed もそれを伝える(データの型は EventBrowserFailed)
session.connect(playwright=None), session.close() Playwright がインストールされていれば(pip install playwright。Playwright 側にブラウザは不要)、CDP で接続した Playwright のブラウザ。close は、それが起動した Playwright を停止する
session.live_url, session.live(interactive=True) ページをブラウザでライブで見る場所:ヘッドレスのセッションでは変化に合わせて描画されるタブ、画面ありのセッションでは画面。閲覧者のマウスとキーボードはページに届く。live(interactive=False) は、ページに何も届かない専用のアドレスを返し、人に見せたり自前のページに埋め込んだりする用途向け。閲覧者は、CDP クライアントと同じくセッションを稼働させ続ける。どちらも rotate_url で新しくなり、キーの url スコープが必要
session.save_profile(name=None), session.profile プロファイルを今すぐ、name(指定しなければ作成時のプロファイル)の最新バージョンとして保存する。作成時に指定した、または最後に保存したプロファイル({"name", "save", "version"})
glac.browsers.profiles.list(), get(name), delete(name) プロジェクトのプロファイル。それぞれ、最新の version、bytes、saved_at と、すべての versions を持つ BrowserProfile
session.proxy, session.proxy_bytes 自前のプロキシ({"url", "username"}。パスワードは含まない)、または None。プロキシを双方向に通過した量(バイト単位。方向ごとの値は session.proxy_sent_bytes と proxy_received_bytes)
with glac.browsers.create(name="agent-42") as session:   # deleted when the block ends
    browser = session.connect()
    page = browser.contexts[0].pages[0]
    page.goto("https://example.com")
    page.screenshot(path="shot.png")

セッションは、ほかのインスタンスと同じく、使用したキューブのリソースで課金されます。処理している間の CPU、稼働中のメモリ、送信トラフィックです(料金とプラン)。session.awake_seconds は稼働していた時間です。

プロファイルとは、訪れたサイトに対してブラウザが誰であるかを示すものです。Cookie、Local Storage、IndexedDB、Session Storage、設定で、キャッシュは含みません。プラットフォームがプロジェクトのために保持し、単独では課金されず、セッションがそのキーを持つことはありません。一度ログインして保存すれば、そのプロファイルで作成したセッションはすべてログイン済みになります。

session = glac.browsers.create(profile={"name": "acme-login", "save": True})   # saved again when it is deleted
browser = session.connect()
# ... log in ...
session.save_profile()                       # the browser restarts, and a connected client reconnects
later = glac.browsers.create(profile="acme-login")   # logged in

保存では、ブラウザは保持しているものを書き出せるように、閉じるときと同じ手順で停止され、再び起動されます。読み込みでは、セッションを返す前に、セッションのブラウザがプロファイルを使って起動されます(1 MB のプロファイルで数百ミリ秒)。1 つのプロファイルの保存は一度に 1 つで、保存の途中のほかの保存は 409 profile_busy で拒否されます。削除したプロファイルはすぐに消え、アーカイブは 30 日後に削除されます。

有料プランでは、セッションの Web トラフィックを、CONNECT を受け付ける自前の HTTP プロキシ経由で送信できます(無料プランでは 403 proxy_not_on_plan)。ポート 80 と 443 への接続は、ブラウザの隣にあるリレーによってプロキシを経由し、ブラウザには何も知らされません。そこでは QUIC は拒否されるため、ブラウザは TCP 上の HTTP で通信します。認証情報はプラットフォームが保持し、どのレスポンスにも含まれません(session.proxy に表示されるのはアドレスとユーザー名です)。

session = glac.browsers.create(name="agent-42", proxy="http://user:password@proxy.example.com:3128")
session.update(proxy="http://other:password@proxy.example.com:3128")   # the browser goes on; its next connections go through this one
session.update(proxy=None)                                             # none
print(session.refresh().proxy_bytes)                                   # what went through it, both ways

proxy は {"url": ...} も受け付けます。プロキシがトンネルを受け付けないページには、HTTP なら 502 proxy_failed が返ります(HTTPS ならハンドシェイクに失敗します)。プロキシを通過したものは、ほかと同じくセッションのトラフィックです。自前のプロキシを参照してください。

セッションの ID はサンドボックスの ID です。glac.sandboxes.get(session.id) で、中でコマンドを実行できます。Playwright の非同期 API では、await p.chromium.connect_over_cdp(session.cdp_url) が connect と同じことをします。セッションはコントロールプレーンの機能です。

データベース

データベースはマネージド Postgres です。ドライブ上で動く専用の PostgreSQL で、何も要求がないときはスリープし、接続があればセッションを保ったまま復帰します。約 2 秒で作成され、ルートブランチ main と、オーナー glacdb_owner(glac_superuser のメンバーで、スーパーユーザーではない)を持ちます。オーナーのパスワードは create と reset_password のレスポンスにしか含まれず、どこにも保存されません。そのどちらかが返したデータベースの connection_string() には、パスワードが含まれます。

import psycopg

db = glac.databases.create("shop")                     # ready, with the owner's password
with psycopg.connect(db.connection_string()) as conn:  # from a sandbox or a service of the project
    conn.execute("create table orders (id bigserial primary key, total numeric)")
呼び出し 内容
glac.databases.create(name, version=, region=, database_name=, size=, storage=, sleep_after_seconds=, suspend_after_seconds=, wake_daily_at=, metadata=, history_seconds=, tiering=) データベースを作成し、最初の起動を待つ:ready、または creating(準備ができるまで db.refresh())。version:16、17、18(デフォルト)。size:{"vcpus", "memory_bytes"}、上限(デフォルトは 1 vCPU と 1 GiB)。storage:{"size_bytes", "durability"}、ドライブで、strict(デフォルト:コミットはオブジェクトストアに届いてから戻る)か standard。sleep_after_seconds:1〜3600 秒(デフォルトは 1 秒)。suspend_after_seconds:メモリを保存して本当にスリープするまで一時停止したままでいる時間で、その間の接続には 1〜2 ミリ秒で応答する。1〜86400 秒(デフォルトは 300 秒)。wake_daily_at:休止中に毎日復帰させる時刻(UTC、"HH:MM")(pg_cron のジョブは、いずれにせよそれぞれの時刻に復帰させる)。既存のデータベースの名前を指定すると、パスワードなしでそのデータベースが返される。glac.databases.update(db, suspend_after_seconds=, wake_daily_at=)(と db.update(...))は、この 2 つを全ブランチまとめて変更し、None でそれぞれ元に戻す(300 と、なし)。無料プランでは、Neon の無料プランがゼロまでスケールするのと同じく、データベースの休止の設定は固定:最後のクエリの 1 秒後に一時停止し(sleep_after_seconds は 1)、その 5 分後にスリープする(suspend_after_seconds は 300)。ほかの値は、作成でも変更でも 400 invalid_field で拒否される
glac.databases.get(id_or_name), list(name=), delete(db, final_snapshot=True) 1 つを取得。一覧(新しい順)。ブランチごと削除し、final_snapshot=False でない限り、ドライブのスナップショットを 7 日間保持する(db.final_snapshot)。パスワードが含まれるのは、作成とリセットのレスポンスだけ
glac.databases.branches(db), branch(db, "main"), sleep(db, "main"), wake(db, "main") ブランチ(最初に main、次に独自のブランチを古い順)、そのうちの 1 つ、そしてブランチをすぐにスリープ、または復帰させる(メモリは保存され、復帰したときにはセッションもそのまま。接続でも復帰する)
glac.databases.create_branch(db, name, parent=, expires_at=), db.create_branch(name, ...); glac.databases.delete_branch(db, branch), db.delete_branch(branch), branch.delete() 独自のブランチ:親(parent で別のものを指定しなければ main)の現在の状態を、何もコピーせずに引き継ぎ、プロジェクトのネットワーク上の <database>-<name> に専用の PostgreSQL、エンドポイント、ドライブを持つ。expires_at を指定すると、その時刻に自動で削除される。ロールとパスワードは親のもので、作成元の Database がオーナーのパスワードを持っていれば、ブランチの connection_string() にもそれが含まれる。削除は PostgreSQL を止めてドライブを削除する。main(409 root_branch)や、ほかのブランチの作成元になったブランチ(409 has_branches)は削除できない
glac.databases.create(..., history_seconds=), update(db, history_seconds=), restore(db, at=, exact=), create_branch(db, name, at=, exact=); db.update(history_seconds=), db.restore(at=, exact=), db.history_seconds, db.history; create(..., tiering=), update(db, tiering=), db.info["tiering"] 履歴(slice DB8 と DB5):時刻を指定して、ルートブランチをどこまで遡って復元できるか、またはブランチを作れるか。指定しなければ 1 日で、有料プランでは 0(なし)〜30 日、無料プランでは 1 日。復元はルートブランチをその場で戻す。分ちょうどの時刻やバージョン自体の時点なら、その時刻以前で最新の、ドライブのウィンドウのバージョンへ(書き込みがあった 30 秒ごとに 1 つ、最大 1 日分)。それより前や秒単位の時刻、または exact=True を指定した場合は、WAL アーカイブによってその時点そのものへミリ秒単位で戻す(WAL をその時点まで再生する。数秒〜数分かかり、呼び出しは待つ)。エンドポイント、接続文字列、オーナーのパスワードは変わらず、接続は切断され、復元前の状態は独自のブランチ main_old_<the restore's moment> として保持される。新しいバージョンが 5 分以内に応答しない復元は取り消される(503 database_restore_failed)。時刻を指定したブランチは、代わりにその時点から作られる(from: {at, exact} として送られる)。履歴にない時刻は 400 invalid_history になり、そのメッセージは、ウィンドウとアーカイブがいつから遡れるかを示す。秒単位の時刻(または exact)はアーカイブから戻すか拒否され、その前のウィンドウのバージョンが使われることはない。アーカイブが届かない場合(アーカイブを保持しないデータベース、または最も古いベースより前の時刻)は 400 invalid_history、アーカイブのストアがセグメントを拒否している間は 503 database_archive_unavailable(再試行可能)。どの拒否も、その時刻以前のウィンドウのバージョンを示すので、代わりにそのバージョン自体の時点を指定できる。ストアがブロックを返さない場合は 503 drive_unreadable(再試行可能)で拒否され、何も作られない。オブジェクトストアの障害が長引き、ドライブがログのために保持できる量を超える WAL が待つことになると、データベースを動かし続けるためにその WAL は手放される。これがアーカイブの欠落で、db.history["gaps"](DatabaseHistoryGap:from、to、from_lsn、to_lsn)に示される。手放した WAL の開始時刻、アーカイブがどの時刻も復元できない期間の終わり(その後に取った最初のベースの終わり。それまでは null)、ログ内での開始位置と終了位置。欠落の間の時刻は、ドライブのウィンドウからしか復元できず(分ちょうどの時刻、またはバージョン自体の時点)、秒単位や exact では 400 invalid_history。欠落はイベントでも伝えられる。最初に見つかったとき(データベースの稼働中に、欠落ごとに 1 回)に database.history_gap、その後アーカイブが再び復元できるようになったときに database.history_resumed。その data は EventDatabaseGap:データベース、欠落の from、to(前者では None)、from_lsn と to_lsn、segments(手放した 16 MB のセグメントの数)、そして前者では error(オブジェクトストアの最後の拒否の内容)。db.history は現在の状態を示す:from(今復元できる最も早い時刻。最初のバージョンまで、つまり準備ができてから約 3 分間は None)と stored_bytes。tiering=True または False で階層型ストレージのオンとオフを切り替え(slice DB18:有料プランのみ。そのために各ブランチの PostgreSQL が 1 回再起動する)、示される tiering は、オンかどうかと、現在の課金の基準(tiered_bytes)
glac.databases.reset_password(db, role=) オーナーの新しいパスワード。このレスポンスにだけ含まれる。古いパスワードは次のログインから拒否されるが、開いている接続はそのまま
glac.databases.roles(db), reset_role_password(db, role); db.roles(), db.reset_role_password(role) ルートブランチのログインするロール。PostgreSQL 自体から読み取る({"name", "owner"}):オーナーと、作成したロール(CREATE ROLE … LOGIN)で、プラットフォームのロールは含まない。そして、そのどれかの新しいパスワード。このレスポンスにだけ含まれる:ロールの password と、そのロールでのルートブランチの接続文字列(connection_string())を持つ DatabaseRoleSecret。オーナーの接続文字列は、API が返すデータベースから作られる。独自のブランチは、作成時のパスワードを保持する
db.connection_string(password=, pooled=, private=) psycopg、psql、SQLAlchemy の postgresql+psycopg:// にそのまま渡せる接続文字列:API が返す場合はパブリックなもの、そうでなければプロジェクトのネットワーク上のもので、一緒に返されたレスポンスのオーナーのパスワード、または password(パーセントエンコード)入り
db.default_branch, branch.connection_uri, pooled_uri, private_uri, endpoint, state API が返すとおりのルートブランチと接続文字列。エンドポイント(ep-<adjective>-<noun>-<8 hex>)。awake、paused、asleep のいずれか。db.refresh()、db.sleep()、db.wake()、db.branches()、db.reset_password()、db.delete() はデータベース自身のメソッド

データベースには、プロジェクトのネットワーク上で、そのサンドボックス(project_network で作成)とサービスから名前でアクセスします。これが private_uri です。エッジが PostgreSQL を提供する場合は、パブリックアドレスでどこからでもアクセスできます(connection_uri、ポート 5432、TLS のみ。プーラーは pooled_uri)。提供するエッジがない場合や、データベースに名前を付けるリージョンがない場合は None で、接続文字列は、それらがあればそれらを使います。repr も、ここにあるほかのどれも、パスワードを表示しません。データベースのアプリとドライブは managed_by とともに一覧表示され、glac.databases を通じてのみ変更されます。データベースはコントロールプレーンの機能です。

マネージドアプリケーション

カタログのアプリケーション(たとえば Rivet)は、プロジェクトに専用のキューブのリソースとしてインストールされます。アプリ、ボリューム、シークレット、バケットで、インストールのラベルが付き、変更はインストールを通じてのみ行い、それぞれのリソースとして課金されます(マネージドアプリケーション)。インストールのレスポンスには、秘密のもの(管理者トークン、それを含むエンドポイント)も含め、すべての出力値があり、読み取りでそれらが表示されるのは admin スコープを持つキーだけです。インストールのオブジェクトは、以前のレスポンスが返し、後のレスポンスが隠した出力値をそれぞれ保持します。

rivet = glac.catalogue.get("rivet")                  # its parameters, and a month of it in Cube's meters
print(rivet.calculator["month"])
engine = glac.installations.create("rivet", parameters={"disk_gib": 20})   # waits until it is ready
print(engine.outputs["endpoint"])                    # RIVET_ENDPOINT, its token in it
呼び出し 内容
glac.catalogue.list(), glac.catalogue.get(app, version=) カタログのアプリケーションの一覧。それぞれ CatalogueApp(app、version、title、summary、publisher、verified、licence、versions、parameters:JSON Schema、outputs、month を含む calculator)。1 つのアプリケーション(最新バージョン、または指定したもの)と、その definition
glac.installations.create(app, name=, version=, region=, parameters=, requires=, wait=True, timeout_seconds=) インストールし、準備ができるまで待つ(wait=False ならすぐに戻る)。失敗すると、その説明と installation とともに InstallationError を送出する。timeout_seconds を過ぎてもまだ作成中なら GlacTimeoutError
glac.installations.list(app=), glac.installations.get(id_or_name) プロジェクトのインストールの一覧(新しい順)。1 つのインストール
installation.update(version=, parameters=, wait=True), installation.delete(), refresh(), wait(timeout_seconds=) 別のバージョンへの変更、またはパラメーターのマージパッチによる変更(None でデフォルトに戻す)。構成するものごとの削除(status:deleted または deleting)
installation.status, failure, outputs, secret_outputs, parameters, apps, volumes, buckets, requires, required_by API が返すとおり

マネージドアプリケーションはコントロールプレーンの機能です。

シークレットと変数

glac.secrets.put("GHCR", "me:ghp_…", allowed_hosts=["ghcr.io"])     # never given back; goes to these hosts only
glac.secrets.update("GHCR", allowed_hosts=["ghcr.io", "*.example.com"])
glac.variables.put("REGION", "eu")                                 # an app's env refers to it: "${{ shared.REGION }}"
print([s.name for s in glac.secrets.list()], {v.name: v.value for v in glac.variables.list()})
glac.secrets.delete("GHCR")

シークレットは名前だけで識別されるため、各呼び出しは名前か、一覧で得た Secret を受け付け、値を含むレスポンスはありません。共有変数はコントロールプレーンの機能です。

プロジェクト、アカウント、請求

呼び出し 内容
glac.project(), glac.events(type=, sandbox=, limit=, cursor=) キーのプロジェクトと、そのイベントの 1 ページ分
glac.usage(start=, end=, group_by=, sandbox=, metadata=, kind=, volume=, drive=, snapshot=, backup=, exact=) 使用量:サンドボックスごと、または 1 時間ごとの帳簿からのレポート
glac.account(), glac.update_account(...) アカウント、その月のトラフィック、通知の送信先(email=。email_verified、そしてそのアドレスへのメールが不達の間は email_undeliverable(EmailUndeliverable):通知の送信先を参照)、通知の言語(language=:“en”、“de”、“fr”、または再び請求先住所に従うなら None)、請求先住所と税番号。コントロールプレーンの背後では keeps(AccountKeeps):キーのプロジェクトが保持するドライブとボリュームを、作成時に適用される数値で、それぞれ持っている量とともに示す(volumes、volume_bytes、volumes_bytes(上限なしなら limit は None)、unsized_bytes:上限の引き上げを参照)
glac.billing(), glac.update_billing(plan=, spend_limit_usd=, start_now=) プランと請求。プラン、利用上限額の変更
glac.balance(), glac.update_balance(auto_reload=, ..., start_now=), glac.top_up(usd, start_now=), glac.charges(), glac.ledger() 保有クレジット、自動チャージ、チャージ、カードへの請求、台帳

Webhook、API キー、利用上限額、プロジェクト。glac.webhooks は、プロジェクトのイベントを自前のアドレスに送ります:create(url, events=, enabled=)(各配信に署名する secret は、このレスポンスにだけ含まれます)、get、list、update、delete。glac.keys はプロジェクトの API キーです:create(name=, scopes=, expires_at=)(キーそのもの key は、このレスポンスにだけ含まれます)、get、list、delete。glac.limits() と glac.update_limits(usd) は、プロジェクトの利用上限額と今月の支出額で(None で上限を外します)、sbx.limits() と sbx.update_limits(usd) はサンドボックス自体のものです。glac.request_limit(limit, wanted, why) は、アカウントの申請による上限の引き上げを 1 つ申請します(名前で指定する上限、"volumes"、"volume_bytes"。バイト数の 2 つはバイト単位。同じ上限の申請が待機中なら 409 limit_request_pending)。glac.limit_requests(limit=, cursor=) と glac.limit_request(id) は、申請をオペレーターの回答とともに読み取ります(LimitRequest:status は pending、granted、refused。granted、answer)。glac.update_project(delete_after_idle_days=30) は、スリープが何日続いたらサンドボックスを削除するかを設定します(None なら削除しない)。コントロールプレーンの機能です。Webhook と API キー、プロジェクトの上限の設定、update_project には admin スコープを持つキーが必要で、サンドボックスの上限の設定には write が必要です。

ログのエクスポート先、監査ログ、請求関連の書類。glac.destinations は、プロジェクトのアプリのログ(とメトリクス、リクエストのトレース:signals=["logs", "traces"])のエクスポート先(OpenTelemetry のエンドポイント)です:create(name, endpoint=, headers=, ...)(作成時にすぐ 1 件のレコードで試す:status["connected"])、get、list、update、delete、test(すぐに 1 件送る)。ヘッダーの値は認証情報で、エクスポート先の作成時か変更時に送られ、どのレスポンスにも含まれません(返されるのはヘッダーの名前だけです)。glac.audit(limit=, cursor=) は、プロジェクトの監査ログの 1 ページ分を新しい順に返します(実行者、メソッド、パス、ステータス)。読み取り以外のすべての呼び出し(拒否されたものも含む)と、オペレーターによるアプリのログの読み取りすべてです。glac.statement("2026-09") は 1 か月の明細(指定しなければ今月。その月の date も可)で、データベースごとの行(databases:その月の使用量、履歴と階層化データを別記したもの、それを定価で換算した額)を含みます。glac.invoices() は Stripe にある請求書(PDF のリンク付き)、glac.notices() はアカウントに送られたメール、glac.withdrawals() は今撤回できる購入と行った撤回(EEA または英国の消費者は、購入から 14 日以内であれば撤回できます)、glac.withdraw(charge_id, name=, email=) は購入者自身による撤回の意思表示で、すぐに受理されます。コントロールプレーンの機能です。エクスポート先の作成、変更、テスト、削除、監査ログの読み取り、撤回には admin スコープを持つキーが、それ以外には読み取りのできるキーが必要です。

どれも API のレスポンスを dict で返し、TypedDict(Account、Billing、Balance、Charge、Project、Event、EventAutoReloadNoRequest、EventInstanceRestarted、EventInstanceUpgraded、EventInstanceReverted、EventInstanceUpgradeFailed、EventLimitRequest など)がその中身を示します。請求、残高、プロジェクトはコントロールプレーンの機能で、単独で動くノードは 404 no_such_route を返します。

**イベントのデータ。**コントロールプレーンのイベントの種類ごとに、その data(event["data"])の TypedDict があり、説明のスキーマにちなんだ名前が付いています。データベースのもの:EventDatabase(database.created:region と PostgreSQL の version 付き。database.deleted:7 日間保持されるドライブのスナップショット final_snapshot_id 付き)、EventDatabaseBranch(database.branch_created:parent 付き。database.branch_deleted:reason 付き)、EventDatabaseRestored(database.restored:時点 at と、復元前の状態を保持するブランチ old_branch_id)、EventDatabaseArchive(database.archive_failing:since、連続して失敗したセグメントの数 failures、その間に復元で遡れる範囲 archived_until、オブジェクトストアの error。database.archive_recovered:recovered_at)、EventDatabaseGap。ほかも同様です。サンドボックスのもの(EventSandboxSlept、EventSandboxWoke、EventSandboxMoved、EventSandboxDeleted など)、アプリとそのキーのもの(EventApp、EventAppFailed、EventKeyCreated など)、プロジェクトのデプロイとキー、バックアップ、ドメイン、エクスポート先、ドライブのスナップショットのもの(EventProjectDeployed、EventBackup、EventDomain、EventDestination、EventDriveSnapshot など)、そしてアカウントのプラン、チャージ、返金、撤回、残高、クレジット、ストレージ、送信量の上限、利用上限額、保留、未払いのもの(EventSubscriptionPeriod、EventTopupCollected、EventWithdrawalReceived、EventBalance、EventInDebt など)です。その種類のすべてのイベントにあるわけではないキーは、型では省略可能になっています。

**すぐに受け取る購入(start_now)。**欧州経済領域または英国の消費者(確認済み、または確認中の税番号を持たないアカウント)は、購入から 14 日以内であれば撤回でき、購入したものがすぐに提供されるのは、本人が明示的に求めた場合だけです。top_up(20, start_now=True)、update_billing(plan="paid", start_now=True)(無料プランから購入する有料プラン)、update_balance(auto_reload=True, start_now=True)(自動チャージがオンの間のすべての自動チャージ)が、その要求を、コンソールのチェックボックスの横に表示される文言(GET /v1/billing/withdrawals の right.start_now にもあるもの)で伝えます。

撤回できる 14 日間のうちに、この購入をすぐに提供するよう求めます。撤回した場合は、それまでに提供された分を支払います。チャージのクレジットをすべて使った時点で全額が提供されたことになり、それ以降は撤回できなくなることを理解しています。

サービスはすぐに始まります。14 日以内なら購入者はまだ撤回でき、その場合はそれまでに提供された分を支払います。すべてが提供されると(チャージのクレジットをすべて使うと)、撤回する権利はなくなります。この要求は、購入する本人が求めたときだけ指定してください。SDK は、指定されない限り決して送りません。指定がなければ、そのようなアカウントは、国名を挙げ、要求の文言を引用したメッセージとともに 400 start_now_required(param は start_now)で拒否され、何も購入されません。要求なしで自動チャージをオンにしたアカウントでは何もチャージされず、月に 1 回通知されます(イベント auto_reload.no_request)。

エラー

0 以外の終了コードは結果です。res.exit_code を読んでください。API のエラーは、どの製品の呼び出しであっても、status、type、code、param、retryable、request_id、message、そしてエラーのページの該当するコードを指す docs_url を持つ GlacError を送出します。接続できない場合は GlacConnectionError、タイムアウトは GlacTimeoutError(TimeoutError でもあります)です。立ち上がらなかったバージョンや失敗したプロジェクトのデプロイは DeployError、断念したログの読み取りは LogsError で、どちらも GlacError です。お金に関するエラー(402 account_in_debt、403 trust_limit_exceeded、409 no_card、400 start_now_required など)は、コントロールプレーン自身のメッセージを持ち、解除する方法を示します。

知っておくべき動作

  • **スリープ中のサンドボックス。**サンドボックスがスリープ中や起動中であることを理由に失敗する呼び出しはありません。待ってから続行します。復帰させるための呼び出しはありません。run() と wait() は待っている呼び出しとして数えられるため、その間サンドボックスは稼働し続けます。cmd.output() の読み取りは数えられません。
  • **再試行。**応答のない呼び出しや retryable のエラーは、0.2 秒から倍々に間隔を空けて 2 回再試行されます(retries=)。すべての POST には Idempotency-Key が付くため、再試行でサンドボックスが 2 つ作られたり、コマンドが 2 回実行されたりすることはありません。
  • タイムアウト。Glac(timeout_seconds=)(指定しなければ 900 秒)は、コマンド自体の時間に加えて API を待つ時間です。run(timeout_seconds=) はコマンドの制限時間で、過ぎるとコマンドは強制終了され、結果は timed_out になります。
  • スレッド。Glac、Sandbox、Command は、複数のスレッドから使えます。asyncio の API はまだないため、await asyncio.to_thread(sbx.run, "make") を使ってください。
ナビゲーション

キーワードを入力して検索…

↑↓ 移動↵ 選択Esc 閉じる