@@ -12,6 +12,22 @@ pub struct SandboxImage {
12
12
name : String ,
13
13
}
14
14
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
+
15
31
impl SandboxImage {
16
32
/// Load a local image present in the host machine.
17
33
///
@@ -27,11 +43,33 @@ impl SandboxImage {
27
43
///
28
44
/// This will access the network to download the image from the registry. If pulling fails an
29
45
/// 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 > {
31
47
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
+ } ;
32
64
info ! ( "pulling image {} from Docker Hub" , name) ;
33
65
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
+ ] )
35
73
. run ( )
36
74
. map_err ( |e| CommandError :: SandboxImagePullFailed ( Box :: new ( e) ) ) ?;
37
75
if let Some ( name_with_hash) = image. get_name_with_hash ( ) {
@@ -146,6 +184,7 @@ pub struct SandboxBuilder {
146
184
user : Option < String > ,
147
185
cmd : Vec < String > ,
148
186
enable_networking : bool ,
187
+ image : Option < String > ,
149
188
}
150
189
151
190
impl SandboxBuilder {
@@ -160,6 +199,7 @@ impl SandboxBuilder {
160
199
user : None ,
161
200
cmd : Vec :: new ( ) ,
162
201
enable_networking : true ,
202
+ image : None ,
163
203
}
164
204
}
165
205
@@ -203,6 +243,14 @@ impl SandboxBuilder {
203
243
self
204
244
}
205
245
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
+
206
254
pub ( super ) fn env < S1 : Into < String > , S2 : Into < String > > ( mut self , key : S1 , value : S2 ) -> Self {
207
255
self . env . push ( ( key. into ( ) , value. into ( ) ) ) ;
208
256
self
@@ -274,7 +322,11 @@ impl SandboxBuilder {
274
322
args. push ( "--isolation=process" . into ( ) ) ;
275
323
}
276
324
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
+ }
278
330
279
331
for arg in self . cmd {
280
332
args. push ( arg) ;
0 commit comments