1111//! `logs`, a global `-j/--json`).
1212
1313use anyhow:: { bail, Context , Result } ;
14- use clap:: { Parser , Subcommand } ;
14+ use clap:: { Parser , Subcommand , ValueEnum } ;
1515use dstack_cli_core:: layout:: InstallLayout ;
1616use dstack_cli_core:: vmm:: { Vmm , DEFAULT_HOST } ;
1717use dstack_cli_core:: { compose, ports, rpc} ;
@@ -77,7 +77,7 @@ enum Command {
7777 ports : Vec < String > ,
7878 /// attach a verity volume, as printed by `dstack verity`: the file name
7979 /// in the vmm's volumes_dir, its verity_root, and the target
80- /// (`docker` or a mount path). Repeatable.
80+ /// (an absolute mount path). Repeatable.
8181 #[ arg( long = "volume" , value_name = "NAME:VERITY_ROOT:TARGET" ) ]
8282 volumes : Vec < String > ,
8383 /// deploy in non-KMS mode (ephemeral keys; no KMS required).
@@ -124,11 +124,29 @@ enum Command {
124124 #[ arg( long, short = 'o' , default_value = "verity.img" ) ]
125125 output : String ,
126126 /// squashfs compression: `none` (the default), `zstd`, or `gzip`.
127- #[ arg( long, default_value = "none " ) ]
128- compress : String ,
127+ #[ arg( long, value_enum , default_value_t , conflicts_with = "fs_image " ) ]
128+ compress : CompressionArg ,
129129 } ,
130130}
131131
132+ #[ derive( Clone , Copy , Default , ValueEnum ) ]
133+ enum CompressionArg {
134+ #[ default]
135+ None ,
136+ Zstd ,
137+ Gzip ,
138+ }
139+
140+ impl From < CompressionArg > for dstack_volume:: Compression {
141+ fn from ( value : CompressionArg ) -> Self {
142+ match value {
143+ CompressionArg :: None => Self :: None ,
144+ CompressionArg :: Zstd => Self :: Zstd ,
145+ CompressionArg :: Gzip => Self :: Gzip ,
146+ }
147+ }
148+ }
149+
132150#[ tokio:: main]
133151async fn main ( ) -> Result < ( ) > {
134152 // progress (e.g. `verity` pulling layers) goes to stderr so it never mixes
@@ -223,37 +241,22 @@ async fn main() -> Result<()> {
223241 fs_image,
224242 output,
225243 compress,
226- } => {
227- cmd_verity (
228- dir. as_deref ( ) ,
229- fs_image. as_deref ( ) ,
230- & output,
231- & compress,
232- json,
233- )
234- . await
235- }
244+ } => cmd_verity ( dir. as_deref ( ) , fs_image. as_deref ( ) , & output, compress, json) . await ,
236245 }
237246}
238247
239248async fn cmd_verity (
240249 dir : Option < & str > ,
241250 fs_image : Option < & str > ,
242251 output : & str ,
243- compress : & str ,
252+ compress : CompressionArg ,
244253 json : bool ,
245254) -> Result < ( ) > {
246- let compress = match compress {
247- "none" => dstack_volume:: Compression :: None ,
248- "zstd" => dstack_volume:: Compression :: Zstd ,
249- "gzip" => dstack_volume:: Compression :: Gzip ,
250- other => bail ! ( "unknown --compress '{other}' (use none|zstd|gzip)" ) ,
251- } ;
252255 let result = dstack_volume:: verity ( dstack_volume:: VerityOptions {
253256 dir : dir. map ( std:: path:: PathBuf :: from) ,
254257 fs_image : fs_image. map ( std:: path:: PathBuf :: from) ,
255258 output : output. into ( ) ,
256- compress,
259+ compress : compress . into ( ) ,
257260 } )
258261 . await ?;
259262
@@ -290,13 +293,6 @@ async fn cmd_verity(
290293 Ok ( ( ) )
291294}
292295
293- /// A parsed `--volume` spec. All fields become part of measured app-compose.
294- struct VolumeSpec {
295- source : String ,
296- verity_root : String ,
297- target : String ,
298- }
299-
300296/// Parse a `--volume` spec `NAME:VERITY_ROOT:TARGET`.
301297///
302298/// `NAME` is the volume file in the vmm's volumes_dir. `VERITY_ROOT` and `TARGET`
@@ -305,7 +301,7 @@ struct VolumeSpec {
305301/// spec to paste.
306302///
307303/// `TARGET` is an absolute read-only mount path in the guest.
308- fn parse_volume ( spec : & str ) -> Result < VolumeSpec > {
304+ fn parse_volume ( spec : & str ) -> Result < dstack_types :: VerityVolume > {
309305 let mut parts = spec. splitn ( 3 , ':' ) ;
310306 let name = parts. next ( ) . unwrap_or_default ( ) ;
311307 let ( root, target) = match ( parts. next ( ) , parts. next ( ) ) {
@@ -326,10 +322,12 @@ fn parse_volume(spec: &str) -> Result<VolumeSpec> {
326322 if !target. starts_with ( '/' ) {
327323 bail ! ( "target '{target}' must be an absolute path" ) ;
328324 }
329- Ok ( VolumeSpec {
325+ let mut verity_root = [ 0 ; 32 ] ;
326+ hex:: decode_to_slice ( root, & mut verity_root) . context ( "decoding verity_root" ) ?;
327+ Ok ( dstack_types:: VerityVolume {
330328 source : name. to_string ( ) ,
331- verity_root : root . to_string ( ) ,
332- target : target. to_string ( ) ,
329+ verity_root,
330+ target : target. into ( ) ,
333331 } )
334332}
335333
@@ -425,11 +423,7 @@ async fn cmd_deploy(
425423
426424 // each --volume declares a measured verity_volumes entry, so the built
427425 // app-compose (and thus app_id) binds the attested roots.
428- let verity_volumes: Vec < ( String , String , String ) > = parsed_volumes
429- . iter ( )
430- . map ( |v| ( v. source . clone ( ) , v. verity_root . clone ( ) , v. target . clone ( ) ) )
431- . collect ( ) ;
432- let app_compose = compose:: build_app_compose ( name, & yaml, !no_kms, & verity_volumes) ;
426+ let app_compose = compose:: build_app_compose ( name, & yaml, !no_kms, & parsed_volumes) ;
433427
434428 let mut cfg = rpc:: VmConfiguration {
435429 name : name. to_string ( ) ,
@@ -669,8 +663,8 @@ mod tests {
669663 let root = "a" . repeat ( 64 ) ;
670664 let data = parse_volume ( & format ! ( "weights.img:{root}:/models/llama" ) ) . unwrap ( ) ;
671665 assert_eq ! ( data. source, "weights.img" ) ;
672- assert_eq ! ( data. verity_root, root ) ;
673- assert_eq ! ( data. target, "/models/llama" ) ;
666+ assert_eq ! ( data. verity_root, [ 0xaa ; 32 ] ) ;
667+ assert_eq ! ( data. target, std :: path :: Path :: new ( "/models/llama" ) ) ;
674668
675669 assert ! ( parse_volume( "weights.img" ) . is_err( ) ) ; // missing verity_root:target
676670 assert ! ( parse_volume( & format!( "weights.img:{root}" ) ) . is_err( ) ) ; // missing target
@@ -734,5 +728,23 @@ mod tests {
734728 "rootfs.ext4"
735729 ] )
736730 . is_err( ) ) ;
731+ assert ! ( Cli :: try_parse_from( [
732+ "dstack" ,
733+ "verity" ,
734+ "--fs-image" ,
735+ "rootfs.ext4" ,
736+ "--compress" ,
737+ "zstd"
738+ ] )
739+ . is_err( ) ) ;
740+ assert ! ( Cli :: try_parse_from( [
741+ "dstack" ,
742+ "verity" ,
743+ "--dir" ,
744+ "data" ,
745+ "--compress" ,
746+ "invalid"
747+ ] )
748+ . is_err( ) ) ;
737749 }
738750}
0 commit comments