Skip to content

Commit 255b743

Browse files
authored
Merge pull request #961 from antheas/progress-docs
docs(progress): Add docs for JSON progress format
2 parents adf18ea + 63d1199 commit 255b743

File tree

9 files changed

+388
-63
lines changed

9 files changed

+388
-63
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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
You can find the JSON schema describing this version here:
16+
[progress-v0.schema.json](progress-v0.schema.json).
17+
18+
Deploying a new image with either switch or upgrade consists
19+
of three stages: `pulling`, `importing`, and `staging`. The `pulling` step
20+
downloads the image from the registry, offering per-layer and progress in
21+
each message. The `importing` step imports the image into storage and consists
22+
of a single step. Finally, `staging` runs a variety of staging
23+
tasks. Currently, they are staging the image to disk, pulling bound images,
24+
and removing old images.
25+
26+
Note that new stages or fields may be added at any time.
27+
28+
Importing and staging are affected by disk speed and the total image size. Pulling
29+
is affected by network speed and how many layers invalidate between pulls.
30+
Therefore, a large image with a good caching strategy will have longer
31+
importing and staging times, and a small bespoke container image will have
32+
negligible importing and staging times.

docs/src/progress-v0.schema.json

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

lib/src/cli.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ pub(crate) struct ProgressOptions {
3737
///
3838
/// Interactive progress will be written to this file descriptor as "JSON lines"
3939
/// format, where each value is separated by a newline.
40-
#[clap(long)]
41-
pub(crate) json_fd: Option<RawProgressFd>,
40+
#[clap(long, hide = true)]
41+
pub(crate) progress_fd: Option<RawProgressFd>,
4242
}
4343

4444
impl TryFrom<ProgressOptions> for ProgressWriter {
4545
type Error = anyhow::Error;
4646

4747
fn try_from(value: ProgressOptions) -> Result<Self> {
4848
let r = value
49-
.json_fd
49+
.progress_fd
5050
.map(TryInto::try_into)
5151
.transpose()?
5252
.unwrap_or_default();
@@ -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(())

0 commit comments

Comments
 (0)