Skip to content

Commit 6759010

Browse files
authored
Merge pull request #981 from cgwalters/progress-minor
cli: Centralize progress parsing a bit
2 parents f443cd4 + 1d5d4dd commit 6759010

File tree

2 files changed

+55
-25
lines changed

2 files changed

+55
-25
lines changed

lib/src/cli.rs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
55
use std::ffi::{CString, OsStr, OsString};
66
use std::io::Seek;
7-
use std::os::fd::RawFd;
87
use std::os::unix::process::CommandExt;
98
use std::process::Command;
109

@@ -26,12 +25,35 @@ use serde::{Deserialize, Serialize};
2625

2726
use crate::deploy::RequiredHostSpec;
2827
use crate::lints;
29-
use crate::progress_jsonl;
30-
use crate::progress_jsonl::ProgressWriter;
28+
use crate::progress_jsonl::{ProgressWriter, RawProgressFd};
3129
use crate::spec::Host;
3230
use crate::spec::ImageReference;
3331
use crate::utils::sigpolicy_from_opts;
3432

33+
/// Shared progress options
34+
#[derive(Debug, Parser, PartialEq, Eq)]
35+
pub(crate) struct ProgressOptions {
36+
/// File descriptor number which must refer to an open pipe (anonymous or named).
37+
///
38+
/// Interactive progress will be written to this file descriptor as "JSON lines"
39+
/// format, where each value is separated by a newline.
40+
#[clap(long)]
41+
pub(crate) json_fd: Option<RawProgressFd>,
42+
}
43+
44+
impl TryFrom<ProgressOptions> for ProgressWriter {
45+
type Error = anyhow::Error;
46+
47+
fn try_from(value: ProgressOptions) -> Result<Self> {
48+
let r = value
49+
.json_fd
50+
.map(TryInto::try_into)
51+
.transpose()?
52+
.unwrap_or_default();
53+
Ok(r)
54+
}
55+
}
56+
3557
/// Perform an upgrade operation
3658
#[derive(Debug, Parser, PartialEq, Eq)]
3759
pub(crate) struct UpgradeOpts {
@@ -54,9 +76,8 @@ pub(crate) struct UpgradeOpts {
5476
#[clap(long, conflicts_with = "check")]
5577
pub(crate) apply: bool,
5678

57-
/// Pipe download progress to this fd in a jsonl format.
58-
#[clap(long)]
59-
pub(crate) json_fd: Option<RawFd>,
79+
#[clap(flatten)]
80+
pub(crate) progress: ProgressOptions,
6081
}
6182

6283
/// Perform an switch operation
@@ -107,9 +128,8 @@ pub(crate) struct SwitchOpts {
107128
/// Target image to use for the next boot.
108129
pub(crate) target: String,
109130

110-
/// Pipe download progress to this fd in a jsonl format.
111-
#[clap(long)]
112-
pub(crate) json_fd: Option<RawFd>,
131+
#[clap(flatten)]
132+
pub(crate) progress: ProgressOptions,
113133
}
114134

115135
/// Options controlling rollback
@@ -653,11 +673,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
653673
let (booted_deployment, _deployments, host) =
654674
crate::status::get_status_require_booted(sysroot)?;
655675
let imgref = host.spec.image.as_ref();
656-
let prog = opts
657-
.json_fd
658-
.map(progress_jsonl::ProgressWriter::from_raw_fd)
659-
.transpose()?
660-
.unwrap_or_default();
676+
let prog: ProgressWriter = opts.progress.try_into()?;
661677

662678
// If there's no specified image, let's be nice and check if the booted system is using rpm-ostree
663679
if imgref.is_none() {
@@ -774,11 +790,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
774790
);
775791
let target = ostree_container::OstreeImageReference { sigverify, imgref };
776792
let target = ImageReference::from(target);
777-
let prog = opts
778-
.json_fd
779-
.map(progress_jsonl::ProgressWriter::from_raw_fd)
780-
.transpose()?
781-
.unwrap_or_default();
793+
let prog: ProgressWriter = opts.progress.try_into()?;
782794

783795
// If we're doing an in-place mutation, we shortcut most of the rest of the work here
784796
if opts.mutate_in_place {

lib/src/progress_jsonl.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
//! see <https://jsonlines.org/>.
33
44
use anyhow::Result;
5-
use fn_error_context::context;
65
use serde::Serialize;
76
use std::borrow::Cow;
87
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
8+
use std::str::FromStr;
99
use std::sync::Arc;
1010
use std::time::Instant;
1111
use tokio::io::{AsyncWriteExt, BufWriter};
@@ -131,6 +131,22 @@ pub enum Event<'t> {
131131
},
132132
}
133133

134+
#[derive(Debug, Clone, PartialEq, Eq)]
135+
pub(crate) struct RawProgressFd(RawFd);
136+
137+
impl FromStr for RawProgressFd {
138+
type Err = anyhow::Error;
139+
140+
fn from_str(s: &str) -> Result<Self> {
141+
let fd = s.parse::<u32>()?;
142+
// Sanity check
143+
if matches!(fd, 0..=2) {
144+
anyhow::bail!("Cannot use fd {fd} for progress JSON")
145+
}
146+
Ok(Self(fd.try_into()?))
147+
}
148+
}
149+
134150
#[derive(Debug)]
135151
struct ProgressWriterInner {
136152
last_write: Option<std::time::Instant>,
@@ -163,14 +179,16 @@ impl From<Sender> for ProgressWriter {
163179
}
164180
}
165181

166-
impl ProgressWriter {
167-
/// Given a raw file descriptor, create an instance of a json-lines writer.
182+
impl TryFrom<RawProgressFd> for ProgressWriter {
183+
type Error = anyhow::Error;
184+
168185
#[allow(unsafe_code)]
169-
#[context("Creating progress writer")]
170-
pub(crate) fn from_raw_fd(fd: RawFd) -> Result<Self> {
171-
unsafe { OwnedFd::from_raw_fd(fd) }.try_into()
186+
fn try_from(fd: RawProgressFd) -> Result<Self> {
187+
unsafe { OwnedFd::from_raw_fd(fd.0) }.try_into()
172188
}
189+
}
173190

191+
impl ProgressWriter {
174192
/// Serialize the target object to JSON as a single line
175193
pub(crate) async fn send_impl<T: Serialize>(&self, v: T, required: bool) -> Result<()> {
176194
let mut guard = self.inner.lock().await;

0 commit comments

Comments
 (0)