@@ -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