Skip to content

Commit c2a7d60

Browse files
antheascgwalters
authored andcommitted
docs: Describe --progress-fd, add rendered JSON schema
Signed-off-by: Antheas Kapenekakis <[email protected]> Signed-off-by: Colin Walters <[email protected]>
1 parent 70133cc commit c2a7d60

File tree

7 files changed

+292
-11
lines changed

7 files changed

+292
-11
lines changed

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
# Experimental features
5656

5757
- [bootc image](experimental-bootc-image.md)
58+
- [--progress-fd](experimental-progress-fd.md)
5859

5960
# More information
6061

docs/src/bootc-via-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ are stable and will not change.
77
## Using `bootc edit` and `bootc status --json`
88

99
While bootc does not depend on Kubernetes, it does currently
10-
also offere a Kubernetes *style* API, especially oriented
10+
also offer a Kubernetes *style* API, especially oriented
1111
towards the [spec and status and other conventions](https://kubernetes.io/docs/reference/using-api/api-concepts/).
1212

1313
In general, most use cases of driving bootc via API are probably

docs/src/experimental-progress-fd.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
# Interactive progress with `--progress-fd`
3+
4+
This is an experimental feature; tracking issue: <https://github.com/containers/bootc/issues/1016>
5+
6+
While the `bootc status` tooling allows a client to discover the state
7+
of the system, during interactive changes such as `bootc upgrade`
8+
or `bootc switch` it is possible to monitor the status of downloads
9+
or other operations at a fine-grained level with `-progress-fd`.
10+
11+
The format of data output over `--progress-fd` is [JSON Lines](https://jsonlines.org)
12+
which is a series of JSON objects separated by newlines (the intermediate
13+
JSON content is guaranteed not to contain a literal newline).
14+
15+
The current API version is `org.containers.bootc/progress/v1`. You can find
16+
the JSON schema describing this version here:
17+
[progress-v1.schema.json](progress-v1.schema.json).
18+
19+
Deploying a new image with either switch or upgrade consists
20+
of three stages: `pulling`, `importing`, and `staging`. The `pulling` step
21+
downloads the image from the registry, offering per-layer and progress in
22+
each message. The `importing` step imports the image into storage and consists
23+
of a single step. Finally, `staging` runs a variety of staging
24+
tasks. Currently, they are staging the image to disk, pulling bound images,
25+
and removing old images.
26+
27+
Note that new stages or fields may be added at any time.
28+
29+
Importing and staging are affected by disk speed and the total image size. Pulling
30+
is affected by network speed and how many layers invalidate between pulls.
31+
Therefore, a large image with a good caching strategy will have longer
32+
importing and staging times, and a small bespoke container image will have
33+
negligible importing and staging times.

docs/src/progress-v1.schema.json

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Event",
4+
"description": "An event emitted as JSON.",
5+
"oneOf": [
6+
{
7+
"description": "An incremental update to a container image layer download",
8+
"type": "object",
9+
"required": [
10+
"api_version",
11+
"bytes",
12+
"bytes_cached",
13+
"bytes_total",
14+
"description",
15+
"id",
16+
"steps",
17+
"steps_cached",
18+
"steps_total",
19+
"subtasks",
20+
"task",
21+
"type"
22+
],
23+
"properties": {
24+
"api_version": {
25+
"description": "The version of the progress event format.",
26+
"type": "string"
27+
},
28+
"bytes": {
29+
"description": "The number of bytes already fetched.",
30+
"type": "integer",
31+
"format": "uint64",
32+
"minimum": 0.0
33+
},
34+
"bytes_cached": {
35+
"description": "The number of bytes fetched by a previous run.",
36+
"type": "integer",
37+
"format": "uint64",
38+
"minimum": 0.0
39+
},
40+
"bytes_total": {
41+
"description": "Total number of bytes. If zero, then this should be considered \"unspecified\".",
42+
"type": "integer",
43+
"format": "uint64",
44+
"minimum": 0.0
45+
},
46+
"description": {
47+
"description": "A human readable description of the task if i18n is not available.",
48+
"type": "string"
49+
},
50+
"id": {
51+
"description": "A human and machine readable unique identifier for the task (e.g., the image name). For tasks that only happen once, it can be set to the same value as task.",
52+
"type": "string"
53+
},
54+
"steps": {
55+
"description": "The initial position of progress.",
56+
"type": "integer",
57+
"format": "uint64",
58+
"minimum": 0.0
59+
},
60+
"steps_cached": {
61+
"description": "The number of steps fetched by a previous run.",
62+
"type": "integer",
63+
"format": "uint64",
64+
"minimum": 0.0
65+
},
66+
"steps_total": {
67+
"description": "The total number of steps (e.g. container image layers, RPMs)",
68+
"type": "integer",
69+
"format": "uint64",
70+
"minimum": 0.0
71+
},
72+
"subtasks": {
73+
"description": "The currently running subtasks.",
74+
"type": "array",
75+
"items": {
76+
"$ref": "#/definitions/SubTaskBytes"
77+
}
78+
},
79+
"task": {
80+
"description": "A machine readable type (e.g., pulling) for the task (used for i18n and UI customization).",
81+
"type": "string"
82+
},
83+
"type": {
84+
"type": "string",
85+
"enum": [
86+
"ProgressBytes"
87+
]
88+
}
89+
}
90+
},
91+
{
92+
"description": "An incremental update with discrete steps",
93+
"type": "object",
94+
"required": [
95+
"api_version",
96+
"description",
97+
"id",
98+
"steps",
99+
"steps_cached",
100+
"steps_total",
101+
"subtasks",
102+
"task",
103+
"type"
104+
],
105+
"properties": {
106+
"api_version": {
107+
"description": "The version of the progress event format.",
108+
"type": "string"
109+
},
110+
"description": {
111+
"description": "A human readable description of the task if i18n is not available.",
112+
"type": "string"
113+
},
114+
"id": {
115+
"description": "A human and machine readable unique identifier for the task (e.g., the image name). For tasks that only happen once, it can be set to the same value as task.",
116+
"type": "string"
117+
},
118+
"steps": {
119+
"description": "The initial position of progress.",
120+
"type": "integer",
121+
"format": "uint64",
122+
"minimum": 0.0
123+
},
124+
"steps_cached": {
125+
"description": "The number of steps fetched by a previous run.",
126+
"type": "integer",
127+
"format": "uint64",
128+
"minimum": 0.0
129+
},
130+
"steps_total": {
131+
"description": "The total number of steps (e.g. container image layers, RPMs)",
132+
"type": "integer",
133+
"format": "uint64",
134+
"minimum": 0.0
135+
},
136+
"subtasks": {
137+
"description": "The currently running subtasks.",
138+
"type": "array",
139+
"items": {
140+
"$ref": "#/definitions/SubTaskStep"
141+
}
142+
},
143+
"task": {
144+
"description": "A machine readable type (e.g., pulling) for the task (used for i18n and UI customization).",
145+
"type": "string"
146+
},
147+
"type": {
148+
"type": "string",
149+
"enum": [
150+
"ProgressSteps"
151+
]
152+
}
153+
}
154+
}
155+
],
156+
"definitions": {
157+
"SubTaskBytes": {
158+
"description": "An incremental update to e.g. a container image layer download. The first time a given \"subtask\" name is seen, a new progress bar should be created. If bytes == bytes_total, then the subtask is considered complete.",
159+
"type": "object",
160+
"required": [
161+
"bytes",
162+
"bytesCached",
163+
"bytesTotal",
164+
"description",
165+
"id",
166+
"subtask"
167+
],
168+
"properties": {
169+
"bytes": {
170+
"description": "Updated byte level progress",
171+
"type": "integer",
172+
"format": "uint64",
173+
"minimum": 0.0
174+
},
175+
"bytesCached": {
176+
"description": "The number of bytes fetched by a previous run (e.g., zstd_chunked).",
177+
"type": "integer",
178+
"format": "uint64",
179+
"minimum": 0.0
180+
},
181+
"bytesTotal": {
182+
"description": "Total number of bytes",
183+
"type": "integer",
184+
"format": "uint64",
185+
"minimum": 0.0
186+
},
187+
"description": {
188+
"description": "A human readable description of the task if i18n is not available. (e.g., \"OSTree Chunk:\", \"Derived Layer:\")",
189+
"type": "string"
190+
},
191+
"id": {
192+
"description": "A human and machine readable identifier for the task (e.g., ostree chunk/layer hash).",
193+
"type": "string"
194+
},
195+
"subtask": {
196+
"description": "A machine readable type for the task (used for i18n). (e.g., \"ostree_chunk\", \"ostree_derived\")",
197+
"type": "string"
198+
}
199+
}
200+
},
201+
"SubTaskStep": {
202+
"description": "Marks the beginning and end of a dictrete step",
203+
"type": "object",
204+
"required": [
205+
"completed",
206+
"description",
207+
"id",
208+
"subtask"
209+
],
210+
"properties": {
211+
"completed": {
212+
"description": "Starts as false when beginning to execute and turns true when completed.",
213+
"type": "boolean"
214+
},
215+
"description": {
216+
"description": "A human readable description of the task if i18n is not available. (e.g., \"OSTree Chunk:\", \"Derived Layer:\")",
217+
"type": "string"
218+
},
219+
"id": {
220+
"description": "A human and machine readable identifier for the task (e.g., ostree chunk/layer hash).",
221+
"type": "string"
222+
},
223+
"subtask": {
224+
"description": "A machine readable type for the task (used for i18n). (e.g., \"ostree_chunk\", \"ostree_derived\")",
225+
"type": "string"
226+
}
227+
}
228+
}
229+
}
230+
}

lib/src/cli.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,12 @@ pub(crate) enum ImageOpts {
359359
Cmd(ImageCmdOpts),
360360
}
361361

362+
#[derive(Debug, Clone, clap::ValueEnum, PartialEq, Eq)]
363+
pub(crate) enum SchemaType {
364+
Host,
365+
Progress,
366+
}
367+
362368
/// Hidden, internal only options
363369
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
364370
pub(crate) enum InternalsOpts {
@@ -371,7 +377,10 @@ pub(crate) enum InternalsOpts {
371377
},
372378
FixupEtcFstab,
373379
/// Should only be used by `make update-generated`
374-
PrintJsonSchema,
380+
PrintJsonSchema {
381+
#[clap(long)]
382+
of: SchemaType,
383+
},
375384
/// Perform cleanup actions
376385
Cleanup,
377386
/// Proxy frontend for the `ostree-ext` CLI.
@@ -1090,8 +1099,11 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
10901099
.await
10911100
}
10921101
InternalsOpts::FixupEtcFstab => crate::deploy::fixup_etc_fstab(&root),
1093-
InternalsOpts::PrintJsonSchema => {
1094-
let schema = schema_for!(crate::spec::Host);
1102+
InternalsOpts::PrintJsonSchema { of } => {
1103+
let schema = match of {
1104+
SchemaType::Host => schema_for!(crate::spec::Host),
1105+
SchemaType::Progress => schema_for!(crate::progress_jsonl::Event),
1106+
};
10951107
let mut stdout = std::io::stdout().lock();
10961108
serde_json::to_writer_pretty(&mut stdout, &schema)?;
10971109
Ok(())

lib/src/progress_jsonl.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! see <https://jsonlines.org/>.
33
44
use anyhow::Result;
5+
use schemars::JsonSchema;
56
use serde::Serialize;
67
use std::borrow::Cow;
78
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
@@ -20,7 +21,7 @@ pub const API_VERSION: &str = "org.containers.bootc.progress/v1";
2021
/// An incremental update to e.g. a container image layer download.
2122
/// The first time a given "subtask" name is seen, a new progress bar should be created.
2223
/// If bytes == bytes_total, then the subtask is considered complete.
23-
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, Clone)]
24+
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, Clone, JsonSchema)]
2425
#[serde(rename_all = "camelCase")]
2526
pub struct SubTaskBytes<'t> {
2627
/// A machine readable type for the task (used for i18n).
@@ -44,7 +45,7 @@ pub struct SubTaskBytes<'t> {
4445
}
4546

4647
/// Marks the beginning and end of a dictrete step
47-
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, Clone)]
48+
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, Clone, JsonSchema)]
4849
#[serde(rename_all = "camelCase")]
4950
pub struct SubTaskStep<'t> {
5051
/// A machine readable type for the task (used for i18n).
@@ -64,7 +65,7 @@ pub struct SubTaskStep<'t> {
6465
}
6566

6667
/// An event emitted as JSON.
67-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
68+
#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)]
6869
#[serde(
6970
tag = "type",
7071
rename_all = "PascalCase",

xtask/src/xtask.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,14 @@ fn update_generated(sh: &Shell) -> Result<()> {
147147
)
148148
.run()?;
149149
}
150-
let schema = cmd!(sh, "cargo run -q -- internals print-json-schema").read()?;
151-
let target = "docs/src/host-v1.schema.json";
152-
std::fs::write(target, &schema)?;
153-
println!("Updated {target}");
150+
for (of, target) in [
151+
("host", "docs/src/host-v1.schema.json"),
152+
("progress", "docs/src/progress-v1.schema.json"),
153+
] {
154+
let schema = cmd!(sh, "cargo run -q -- internals print-json-schema --of={of}").read()?;
155+
std::fs::write(target, &schema)?;
156+
println!("Updated {target}");
157+
}
154158
Ok(())
155159
}
156160

0 commit comments

Comments
 (0)