11use std:: { collections:: BTreeMap , io:: IsTerminal } ;
22
33use secrecy:: { ExposeSecret , SecretString } ;
4+ use serde:: Serialize ;
45use snafu:: { ResultExt , Snafu } ;
56
67use crate :: {
7- cli:: { ImageCheckArguments , ImageListArguments , Pretty } ,
8+ cli:: { Format , ImageCheckArguments , ImageListArguments , ImageSizeArguments , Pretty } ,
89 config:: Config ,
910 core:: bakefile:: { self , Targets , TargetsOptions } ,
10- models:: TagList ,
11- utils:: { format_image_index_manifest_tag, format_registry_token_env_var_name} ,
11+ models:: { Manifest , TagList } ,
12+ utils:: {
13+ format_image_index_manifest_tag, format_image_manifest_tag,
14+ format_registry_token_env_var_name,
15+ } ,
1216} ;
1317
1418#[ derive( Debug , Snafu ) ]
@@ -22,8 +26,8 @@ pub enum Error {
2226 #[ snafu( display( "failed to build request client" ) ) ]
2327 BuildClient { source : reqwest:: Error } ,
2428
25- #[ snafu( display( "failed to send request" ) ) ]
26- SendRequest { source : reqwest:: Error } ,
29+ #[ snafu( display( "failed to send request ({url}) " ) ) ]
30+ SendRequest { source : reqwest:: Error , url : String } ,
2731
2832 #[ snafu( display( "failed to deserialize response" ) ) ]
2933 DeserializeResponse { source : reqwest:: Error } ,
@@ -51,7 +55,7 @@ pub fn list_images(arguments: ImageListArguments) -> Result<(), Error> {
5155 . context ( BuildTargetsSnafu ) ?
5256 } ;
5357
54- let list = targets
58+ let list: BTreeMap < String , Vec < String > > = targets
5559 . into_iter ( )
5660 . map ( |( image_name, ( image_config, _) ) | {
5761 let versions: Vec < _ > = image_config
@@ -63,7 +67,7 @@ pub fn list_images(arguments: ImageListArguments) -> Result<(), Error> {
6367 } )
6468 . collect ( ) ;
6569
66- print_to_stdout ( list, arguments. pretty )
70+ serialize_to_stdout ( list, arguments. pretty )
6771}
6872
6973pub async fn check_images ( arguments : ImageCheckArguments , config : Config ) -> Result < ( ) , Error > {
@@ -109,7 +113,7 @@ pub async fn check_images(arguments: ImageCheckArguments, config: Config) -> Res
109113 "https://{registry}/v2/{registry_namespace}/{image_name}/tags/list" ,
110114 registry_namespace = registry_options. namespace,
111115 ) ;
112- let request = client. get ( url) ;
116+ let request = client. get ( & url) ;
113117
114118 let request = match & registry_token {
115119 Some ( registry_token) => request. bearer_auth ( registry_token. expose_secret ( ) ) ,
@@ -119,7 +123,7 @@ pub async fn check_images(arguments: ImageCheckArguments, config: Config) -> Res
119123 let tag_list: TagList = request
120124 . send ( )
121125 . await
122- . context ( SendRequestSnafu ) ?
126+ . context ( SendRequestSnafu { url } ) ?
123127 . json ( )
124128 . await
125129 . context ( DeserializeResponseSnafu ) ?;
@@ -148,14 +152,134 @@ pub async fn check_images(arguments: ImageCheckArguments, config: Config) -> Res
148152 Ok ( ( ) )
149153}
150154
151- fn print_to_stdout ( list : BTreeMap < String , Vec < String > > , pretty : Pretty ) -> Result < ( ) , Error > {
155+ pub async fn calculate_size ( arguments : ImageSizeArguments , config : Config ) -> Result < ( ) , Error > {
156+ let targets = if arguments. image . is_empty ( ) {
157+ Targets :: all ( TargetsOptions {
158+ only_entry : true ,
159+ non_recursive : true ,
160+ } )
161+ . context ( BuildTargetsSnafu ) ?
162+ } else {
163+ Targets :: set (
164+ & arguments. image ,
165+ TargetsOptions {
166+ only_entry : true ,
167+ non_recursive : true ,
168+ } ,
169+ )
170+ . context ( BuildTargetsSnafu ) ?
171+ } ;
172+
173+ let mut registry_tokens = BTreeMap :: new ( ) ;
174+ let client = reqwest:: ClientBuilder :: new ( )
175+ . build ( )
176+ . context ( BuildClientSnafu ) ?;
177+
178+ #[ derive( Serialize ) ]
179+ struct SizeResult {
180+ images : BTreeMap < String , u64 > ,
181+ total : u64 ,
182+ }
183+
184+ let mut result = SizeResult {
185+ images : BTreeMap :: new ( ) ,
186+ total : 0 ,
187+ } ;
188+
189+ for ( image_name, ( image_config, _) ) in targets {
190+ for ( registry, registry_options) in image_config. metadata . registries {
191+ // Add tokens to a map so that we don't need construct the key and retrieve the value
192+ // over and over again.
193+ let registry_token = registry_tokens. entry ( registry. clone ( ) ) . or_insert_with ( || {
194+ let name = format_registry_token_env_var_name ( & registry) ;
195+ std:: env:: var ( name) . ok ( ) . map ( SecretString :: from)
196+ } ) ;
197+
198+ for ( image_version, _) in image_config. versions . iter ( ) {
199+ let image_index_manifest_tag = format_image_index_manifest_tag (
200+ image_version,
201+ & config. metadata . vendor_tag_prefix ,
202+ & arguments. image_version ,
203+ ) ;
204+
205+ let manifest_tag = format_image_manifest_tag (
206+ & image_index_manifest_tag,
207+ arguments. target_platform . architecture ( ) ,
208+ // Never strip the architecture, because we need to reference the exact manifest
209+ // to be able to calculate the sizes
210+ false ,
211+ ) ;
212+
213+ let url = format ! (
214+ "https://{registry}/v2/{registry_namespace}/{image_name}/manifests/{manifest_tag}" ,
215+ registry_namespace = registry_options. namespace,
216+ ) ;
217+
218+ let request = client. get ( & url) ;
219+
220+ let request = match & registry_token {
221+ Some ( registry_token) => request. bearer_auth ( registry_token. expose_secret ( ) ) ,
222+ None => request,
223+ } ;
224+
225+ let manifest: Manifest = request
226+ . send ( )
227+ . await
228+ . context ( SendRequestSnafu { url } ) ?
229+ . json ( )
230+ . await
231+ . context ( DeserializeResponseSnafu ) ?;
232+
233+ let layer_size = manifest. layers . iter ( ) . fold ( 0u64 , |acc, e| acc + e. size ) ;
234+
235+ let size = result. images . entry ( image_name. clone ( ) ) . or_default ( ) ;
236+ * size += layer_size;
237+
238+ result. total += layer_size;
239+ }
240+ }
241+ }
242+
243+ match arguments. format {
244+ Format :: Plain => {
245+ if let Some ( max_width) = result
246+ . images
247+ . iter ( )
248+ . map ( |i| i. 0 . len ( ) )
249+ . max_by ( |lhs, rhs| lhs. cmp ( rhs) )
250+ {
251+ // let sorted = result.images;
252+ for ( image, size) in result. images {
253+ println ! (
254+ "{image:max_width$} {size}" ,
255+ size = humansize:: format_size( size, humansize:: BINARY )
256+ ) ;
257+ }
258+
259+ println ! (
260+ "{total_text:max_width$} {total}" ,
261+ total_text = "Total" ,
262+ total = humansize:: format_size( result. total, humansize:: BINARY )
263+ )
264+ }
265+
266+ Ok ( ( ) )
267+ }
268+ Format :: Json => serialize_to_stdout ( & result, arguments. pretty ) ,
269+ }
270+ }
271+
272+ fn serialize_to_stdout < T > ( value : T , pretty : Pretty ) -> Result < ( ) , Error >
273+ where
274+ T : Serialize ,
275+ {
152276 let stdout = std:: io:: stdout ( ) ;
153277
154278 match pretty {
155279 Pretty :: Always | Pretty :: Auto if stdout. is_terminal ( ) => {
156- serde_json:: to_writer_pretty ( stdout, & list )
280+ serde_json:: to_writer_pretty ( stdout, & value )
157281 }
158- _ => serde_json:: to_writer ( stdout, & list ) ,
282+ _ => serde_json:: to_writer ( stdout, & value ) ,
159283 }
160284 . context ( SerializeListSnafu )
161285}
0 commit comments