From 442d49656ca2ce014ca72efd44d64a537c4f69a4 Mon Sep 17 00:00:00 2001 From: Dragos Gheorghioiu Date: Wed, 21 Oct 2026 13:54:37 +0300 Subject: [PATCH] chore(api): Regenerate the build API plumbing The build spec now takes a build without a Kraftfile, which builds the context's Dockerfile on the base-compat runtime, and reads the request's kraftfile and contexts back on the build. The generated models and the docs still presented the Kraftfile as required. Regenerate the build models from the prod-staging spec, and say in the client's docstrings and the README what a build without one is. Signed-off-by: Dragos Gheorghioiu --- README.md | 2 ++ src/unikraft_cloud/api/build/models_gen.py | 34 ++++++++++++++-------- src/unikraft_cloud/resources/builds.py | 13 +++++---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 1376fe0..31ce734 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,8 @@ A build runs on the remote build service, in a builder of your own account, from sources the builder fetches itself: a git repository or a tarball, never a directory on this machine. A create takes the properties the API describes; `kraftfile_from` reads the Kraftfile out of a source, and `image_output` pushes the image with your token. +Without a `kraftfile`, the build is the `Dockerfile` in the context on the +`base-compat:latest` runtime. `logs()` follows the progress as buildkit events, every event once, asking again and skipping what it has seen when the stream is cut, and ends with the event that carries the `result` or the `error`: a failed build is that last event, not an exception. diff --git a/src/unikraft_cloud/api/build/models_gen.py b/src/unikraft_cloud/api/build/models_gen.py index b551ca0..fbdd2e3 100644 --- a/src/unikraft_cloud/api/build/models_gen.py +++ b/src/unikraft_cloud/api/build/models_gen.py @@ -116,6 +116,16 @@ class Build(BaseModel): """The state of the build.""" builder: str | None = None """The name of the builder the build runs on.""" + kraftfile: Kraftfile | None = None + """ + The Kraftfile of the request. Absent for a build without one, and for a + build from before the service kept its requests. + """ + contexts: BuildContexts | None = None + """ + The contexts of the request, where the build gets its files from. Absent + for a build from before the service kept its requests. + """ session: str | None = None """ The path to attach the build's session at. Only present while the build is @@ -147,11 +157,11 @@ class BuildContexts(BaseModel): # Fields the specification does not describe are kept rather than dropped, # so a newly added one survives a round trip and does not break this client. - # A field the specification requires is required here too, so a request - # missing one fails at construction rather than at the server. + # Every field is optional: one response shape serves several requests, and a + # summary listing or a failed bulk item carries only some of them. model_config = ConfigDict(extra="allow", populate_by_name=True) - context: Source + context: Source | None = None """ The build context: the directory the Dockerfile is built in, and the directory relative paths in the Kraftfile resolve in. @@ -220,8 +230,7 @@ class BuildOutput(BaseModel): volume: str | None = None """ The name or UUID of an existing volume to import the built filesystem - into. Only valid for a plain image of a single platform. Not supported - yet. + into. Only valid for a build of a single platform. Not supported yet. """ metro: str | None = None """ @@ -335,17 +344,18 @@ class CreateBuildRequest(BaseModel): kraftfile: Kraftfile | None = None """ - The Kraftfile to build, which makes the build a Unikraft image. Without - one, the build is a plain image of `context`, built with the Dockerfile in - `dockerfile`. + The Kraftfile to build. Without one, the build uses a Kraftfile with + `runtime: base-compat:latest` and `rootfs: ./Dockerfile`, so its rootfs + is `contexts.context` built with the file `Dockerfile`, or with the one + that `contexts.dockerfile` gives. """ contexts: BuildContexts """The contexts to build from.""" platforms: list[str] | None = None """ The platforms to build for. Only the architecture selects, so `x86_64` is - `linux/amd64`. Defaults to the targets of the Kraftfile or of its runtime, - or to the builder's own platform for a plain image. + `linux/amd64`. Defaults to the targets of the Kraftfile, or to `x86_64` + when it has none. """ build_args: dict[str, str] | None = None """The values of the Dockerfile's `ARG`s, by name.""" @@ -537,8 +547,8 @@ class Source(BaseModel): # Fields the specification does not describe are kept rather than dropped, # so a newly added one survives a round trip and does not break this client. - # A field the specification requires is required here too, so a request - # missing one fails at construction rather than at the server. + # Every field is optional: one response shape serves several requests, and a + # summary listing or a failed bulk item carries only some of them. model_config = ConfigDict(extra="allow", populate_by_name=True) session: str | None = None diff --git a/src/unikraft_cloud/resources/builds.py b/src/unikraft_cloud/resources/builds.py index 387738d..ca76c48 100644 --- a/src/unikraft_cloud/resources/builds.py +++ b/src/unikraft_cloud/resources/builds.py @@ -2,11 +2,12 @@ # Copyright (c) 2026, Unikraft GmbH. All rights reserved. # # Builds on the remote build service: a request names the sources, the -# Kraftfile and the outputs, the build runs on a builder in the caller's own -# account, and its progress can be followed from the start until it ends. The -# service is global, so no metro scope applies. Every source is a git -# repository or a tarball the builder fetches itself: a directory on this -# machine would need a buildkit session, which the service does not take yet. +# outputs and a Kraftfile, or no Kraftfile to build the context's Dockerfile +# alone; the build runs on a builder in the caller's own account, and its +# progress can be followed from the start until it ends. The service is +# global, so no metro scope applies. Every source is a git repository or a +# tarball the builder fetches itself: a directory on this machine would need +# a buildkit session, which the service does not take yet. from __future__ import annotations @@ -165,6 +166,8 @@ def __init__(self, session: Session) -> None: async def create(self, *, timeout: TimeoutOption = UNSET, **spec: Any) -> Build: """Create a build from the properties the API describes; it runs whether or not followed. + Without a ``kraftfile``, the build is the ``Dockerfile`` in the + ``context`` on the ``base-compat:latest`` runtime, for ``x86_64``. A property the API does not describe is a ``TypeError``. The service refuses a request that uses a part of the API it does not support yet with the code ``unsupported``, and a tenant with as many builds