Skip to main content

zlayer_types/api/
images.rs

1//! Image management API DTOs.
2//!
3//! Wire-format types shared between the daemon's `/api/v1/images` and
4//! `/api/v1/system/prune` endpoints and SDK clients. Moved out of
5//! `zlayer-api` so SDK crates can depend on them without pulling in the
6//! full server stack.
7
8use serde::{Deserialize, Serialize};
9use utoipa::{IntoParams, ToSchema};
10
11/// Serializable wrapper for `zlayer_agent::runtime::ImageInfo` so we can
12/// attach `ToSchema` here (the underlying type in `zlayer-agent` can't
13/// depend on `utoipa`).
14#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
15pub struct ImageInfoDto {
16    /// Canonical image reference (e.g. `zachhandley/zlayer-manager:latest`).
17    #[schema(value_type = String)]
18    #[serde(with = "crate::image_ref_serde")]
19    pub reference: crate::ImageReference,
20    /// Content-addressed digest (`sha256:...`) if known.
21    pub digest: Option<String>,
22    /// Size in bytes if known.
23    pub size_bytes: Option<u64>,
24}
25
26/// Serializable wrapper for `zlayer_agent::runtime::PruneResult`.
27#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
28pub struct PruneResultDto {
29    /// Image references or digests that were removed.
30    pub deleted: Vec<String>,
31    /// Bytes reclaimed from the cache.
32    pub space_reclaimed: u64,
33}
34
35/// Request body for the pull-image handler. Blocking pull of an OCI image.
36#[derive(Debug, Clone, Deserialize, ToSchema)]
37pub struct PullImageRequest {
38    /// OCI image reference to pull, e.g. `docker.io/library/nginx:latest`.
39    #[schema(value_type = String)]
40    #[serde(with = "crate::image_ref_serde")]
41    pub reference: crate::ImageReference,
42    /// Pull policy override. Accepts `"always"`, `"if_not_present"`, or
43    /// `"never"`. Defaults to `"always"` when omitted.
44    #[serde(default)]
45    pub pull_policy: Option<String>,
46    // -- ยง3.10: registry auth -----------------------------------------------
47    /// Id of a persisted registry credential (from
48    /// `POST /api/v1/credentials/registry`) to use for this pull. Ignored
49    /// when [`Self::registry_auth`] is also supplied (inline auth wins).
50    /// Requires the daemon to be configured with a credential store.
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub registry_credential_id: Option<String>,
53    /// Inline Docker/OCI registry credentials used for this pull only. Not
54    /// persisted, never logged, never echoed back on a response. Takes
55    /// precedence over `registry_credential_id`.
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub registry_auth: Option<crate::spec::RegistryAuth>,
58}
59
60/// Response body for the pull-image handler. Reports the pulled reference
61/// and, when the backend exposes it via `list_images`, the resolved digest
62/// and on-disk size.
63#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
64pub struct PullImageResponse {
65    /// Canonical reference that was pulled.
66    #[schema(value_type = String)]
67    #[serde(with = "crate::image_ref_serde")]
68    pub reference: crate::ImageReference,
69    /// Content-addressed digest (`sha256:...`) if the runtime reports one.
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub digest: Option<String>,
72    /// On-disk size in bytes if the runtime reports one.
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub size_bytes: Option<u64>,
75}
76
77/// Query parameters for the remove-image handler.
78#[derive(Debug, Deserialize, IntoParams)]
79pub struct RemoveImageQuery {
80    /// Force removal even if the image is referenced by containers.
81    #[serde(default)]
82    pub force: bool,
83}
84
85/// Query parameters for the pull-image handler.
86///
87/// When `stream=true`, the handler returns a Newline-Delimited-JSON (NDJSON)
88/// stream of [`PullProgressDto`] events instead of a single
89/// [`PullImageResponse`] JSON object. Each event is one JSON object on its
90/// own line, terminated with `\n`. The stream closes after a single
91/// `done` event (or an error).
92#[derive(Debug, Default, Deserialize, IntoParams)]
93pub struct PullImageQuery {
94    /// When `true`, stream NDJSON `PullProgressDto` events instead of a
95    /// single JSON response. Defaults to `false` (snapshot pull).
96    #[serde(default)]
97    pub stream: bool,
98}
99
100/// One progress event emitted by the streaming pull endpoint.
101///
102/// Wire-format mirror of `zlayer_agent::runtime::PullProgress`. Lives in
103/// `zlayer-types` so SDK clients can deserialize the streamed events
104/// without depending on the server-side `zlayer-agent` crate.
105///
106/// Tagged on `kind` so the JSON shape is self-describing on the wire:
107/// `{"kind":"status",...}` for in-flight progress and
108/// `{"kind":"done",...}` for the terminal completion event.
109#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
110#[serde(tag = "kind", rename_all = "snake_case")]
111pub enum PullProgressDto {
112    /// Progress update for an in-flight layer or stage.
113    Status {
114        /// Layer ID or other backend-specific identifier, when available.
115        #[serde(default, skip_serializing_if = "Option::is_none")]
116        id: Option<String>,
117        /// Human-readable status text, e.g. `"Pulling fs layer"`,
118        /// `"Downloading"`, `"Extracting"`. Always present; may be empty
119        /// when the backend has nothing to report this tick.
120        status: String,
121        /// Pre-formatted progress bar string, when the backend reports one.
122        #[serde(default, skip_serializing_if = "Option::is_none")]
123        progress: Option<String>,
124        /// Bytes transferred so far for this layer, when reported.
125        #[serde(default, skip_serializing_if = "Option::is_none")]
126        current: Option<u64>,
127        /// Expected total bytes for this layer, when reported.
128        #[serde(default, skip_serializing_if = "Option::is_none")]
129        total: Option<u64>,
130    },
131    /// Pull completed successfully. Terminal event; the stream closes
132    /// after this is emitted.
133    Done {
134        /// Resolved canonical image reference.
135        reference: String,
136        /// Content-addressed digest, when the backend reports one.
137        #[serde(default, skip_serializing_if = "Option::is_none")]
138        digest: Option<String>,
139    },
140}
141
142/// Request body for the tag-image handler. Matches Docker-compat
143/// `docker tag` semantics: create a new reference (`target`) pointing at an
144/// already-cached image (`source`).
145#[derive(Debug, Clone, Deserialize, ToSchema)]
146pub struct TagImageRequest {
147    /// Existing image reference to tag (e.g. `myapp:latest`).
148    #[schema(value_type = String)]
149    #[serde(with = "crate::image_ref_serde")]
150    pub source: crate::ImageReference,
151    /// New reference to create (e.g. `registry.example.com/myapp:v1`).
152    #[schema(value_type = String)]
153    #[serde(with = "crate::image_ref_serde")]
154    pub target: crate::ImageReference,
155}
156
157/// One row of an image's history. Mirror of
158/// `zlayer_agent::runtime::ImageHistoryEntry`. Lives here so SDK clients
159/// can deserialize without depending on the server-side agent crate.
160#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
161pub struct ImageHistoryEntryDto {
162    /// Layer / image id (`sha256:...`). May be `<missing>` for layers that
163    /// were dropped during a squash.
164    pub id: String,
165    /// Unix-seconds timestamp when this layer was created.
166    pub created: i64,
167    /// Dockerfile-style instruction that produced this layer.
168    pub created_by: String,
169    /// Tags that point at this specific layer.
170    pub tags: Vec<String>,
171    /// Layer size in bytes.
172    pub size: u64,
173    /// Optional comment recorded with the layer.
174    pub comment: String,
175}
176
177/// One result returned by the search-images endpoint.
178#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
179pub struct ImageSearchResultDto {
180    /// Image name (e.g. `library/nginx`).
181    pub name: String,
182    /// Free-text description.
183    pub description: String,
184    /// Number of stars on the source registry, when reported.
185    pub star_count: u64,
186    /// Whether the image is officially curated.
187    pub official: bool,
188    /// Whether the image was produced by an automated build (deprecated).
189    pub automated: bool,
190}
191
192/// Query parameters for the search-images handler.
193#[derive(Debug, Default, Deserialize, IntoParams)]
194pub struct SearchImagesQuery {
195    /// Search term โ€” image name or substring.
196    pub term: String,
197    /// Maximum number of results to return. `0` means "let the registry
198    /// decide".
199    #[serde(default)]
200    pub limit: u32,
201}
202
203/// Body for the commit-container endpoint.
204#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
205pub struct CommitContainerRequest {
206    /// Container id or name to commit.
207    pub container: String,
208    /// Repository name to apply (e.g. `myapp`). Optional.
209    #[serde(default, skip_serializing_if = "Option::is_none")]
210    pub repo: Option<String>,
211    /// Tag to apply (defaults to `latest` when `repo` is set).
212    #[serde(default, skip_serializing_if = "Option::is_none")]
213    pub tag: Option<String>,
214    /// Free-form comment.
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub comment: Option<String>,
217    /// Author.
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub author: Option<String>,
220    /// Whether to pause the container before committing (default `true`).
221    #[serde(default = "default_true")]
222    pub pause: bool,
223    /// Dockerfile-style instructions to apply during commit.
224    #[serde(default, skip_serializing_if = "Option::is_none")]
225    pub changes: Option<String>,
226}
227
228const fn default_true() -> bool {
229    true
230}
231
232/// Response body for the commit-container endpoint.
233#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
234pub struct CommitContainerResponse {
235    /// Content-addressed image id of the new image.
236    pub id: String,
237}
238
239/// Body for the import-image endpoint.
240#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
241pub struct ImportImageRequest {
242    /// Repository name to apply (optional).
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub repo: Option<String>,
245    /// Tag to apply (optional, defaults to `latest` when `repo` is set).
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub tag: Option<String>,
248}
249
250/// Response body for the import-image endpoint.
251#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
252pub struct ImportImageResponse {
253    /// Content-addressed image id of the imported image.
254    pub id: String,
255}
256
257/// Query parameters for the load-images endpoint.
258#[derive(Debug, Default, Deserialize, IntoParams)]
259pub struct LoadImagesQuery {
260    /// Suppress per-line progress output.
261    #[serde(default)]
262    pub quiet: bool,
263}
264
265/// Query parameters for the save-images endpoint. Names are repeatable โ€”
266/// `?names=alpine&names=nginx:1.21` produces a multi-image archive.
267#[derive(Debug, Default, Deserialize, IntoParams)]
268pub struct SaveImagesQuery {
269    /// Image names to include in the tar archive.
270    #[serde(default)]
271    pub names: Vec<String>,
272}