@@ -12,6 +12,22 @@ pub struct SandboxImage {
1212    name :  String , 
1313} 
1414
15+ #[ derive( serde:: Deserialize ) ]  
16+ struct  DockerManifest  { 
17+     config :  DockerManifestConfig , 
18+     layers :  Vec < DockerManifestLayer > , 
19+ } 
20+ 
21+ #[ derive( serde:: Deserialize ) ]  
22+ struct  DockerManifestConfig  { 
23+     digest :  String , 
24+ } 
25+ 
26+ #[ derive( serde:: Deserialize ) ]  
27+ struct  DockerManifestLayer  { 
28+     size :  usize , 
29+ } 
30+ 
1531impl  SandboxImage  { 
1632    /// Load a local image present in the host machine. 
1733/// 
@@ -27,11 +43,33 @@ impl SandboxImage {
2743/// 
2844/// This will access the network to download the image from the registry. If pulling fails an 
2945/// error will be returned instead. 
30- pub  fn  remote ( name :  & str )  -> Result < Self ,  CommandError >  { 
46+ pub  fn  remote ( name :  & str ,   size_limit :   Option < usize > )  -> Result < Self ,  CommandError >  { 
3147        let  mut  image = SandboxImage  {  name :  name. into ( )  } ; 
48+         let  digest = if  let  Some ( size_limit)  = size_limit { 
49+             let  out = Command :: new_workspaceless ( "docker" ) 
50+                 . args ( & [ "manifest" ,  "inspect" ,  name] ) 
51+                 . run_capture ( ) ?
52+                 . stdout_lines ( ) 
53+                 . join ( "\n " ) ; 
54+             let  m:  DockerManifest  = serde_json:: from_str ( & out) 
55+                 . map_err ( CommandError :: InvalidDockerManifestInspectOutput ) ?; 
56+             let  size = m. layers . iter ( ) . fold ( 0 ,  |acc,  l| acc + l. size ) ; 
57+             if  size > size_limit { 
58+                 return  Err ( CommandError :: SandboxImageTooLarge ( size) ) ; 
59+             } 
60+             Some ( m. config . digest ) 
61+         }  else  { 
62+             None 
63+         } ; 
3264        info ! ( "pulling image {} from Docker Hub" ,  name) ; 
3365        Command :: new_workspaceless ( "docker" ) 
34-             . args ( & [ "pull" ,  & name] ) 
66+             . args ( & [ 
67+                 "pull" , 
68+                 & digest. map_or ( name. to_string ( ) ,  |digest| { 
69+                     let  name = name. split ( ':' ) . next ( ) . unwrap ( ) ; 
70+                     format ! ( "{}@{}" ,  name,  digest) 
71+                 } ) , 
72+             ] ) 
3573            . run ( ) 
3674            . map_err ( |e| CommandError :: SandboxImagePullFailed ( Box :: new ( e) ) ) ?; 
3775        if  let  Some ( name_with_hash)  = image. get_name_with_hash ( )  { 
@@ -146,6 +184,7 @@ pub struct SandboxBuilder {
146184    user :  Option < String > , 
147185    cmd :  Vec < String > , 
148186    enable_networking :  bool , 
187+     image :  Option < String > , 
149188} 
150189
151190impl  SandboxBuilder  { 
@@ -160,6 +199,7 @@ impl SandboxBuilder {
160199            user :  None , 
161200            cmd :  Vec :: new ( ) , 
162201            enable_networking :  true , 
202+             image :  None , 
163203        } 
164204    } 
165205
@@ -203,6 +243,14 @@ impl SandboxBuilder {
203243        self 
204244    } 
205245
246+     /// Override the image used for this sandbox. 
247+ /// 
248+ /// By default rustwide will use the image configured with [`WorkspaceBuilder::sandbox_image`]. 
249+ pub  fn  image ( mut  self ,  image :  SandboxImage )  -> Self  { 
250+         self . image  = Some ( image. name ) ; 
251+         self 
252+     } 
253+ 
206254    pub ( super )  fn  env < S1 :  Into < String > ,  S2 :  Into < String > > ( mut  self ,  key :  S1 ,  value :  S2 )  -> Self  { 
207255        self . env . push ( ( key. into ( ) ,  value. into ( ) ) ) ; 
208256        self 
@@ -274,7 +322,11 @@ impl SandboxBuilder {
274322            args. push ( "--isolation=process" . into ( ) ) ; 
275323        } 
276324
277-         args. push ( workspace. sandbox_image ( ) . name . clone ( ) ) ; 
325+         if  let  Some ( image)  = self . image  { 
326+             args. push ( image) ; 
327+         }  else  { 
328+             args. push ( workspace. sandbox_image ( ) . name . clone ( ) ) ; 
329+         } 
278330
279331        for  arg in  self . cmd  { 
280332            args. push ( arg) ; 
0 commit comments