1- use std:: { collections:: BTreeMap , fmt, process:: Command , result:: Result :: Ok } ;
1+ use std:: borrow:: Cow ;
2+ use std:: process:: Output ;
3+ use std:: thread;
4+ use std:: time:: Duration ;
5+ use std:: { collections:: BTreeMap , fmt, process:: Command , result:: Result :: Ok } ;
26
7+ use anyhow:: Error ;
8+ use colored:: Colorize ;
9+ use k8s_openapi:: apimachinery:: pkg:: version;
310use kube:: core:: ErrorResponse ;
411use serde:: Serialize ;
5- use colored:: Colorize ;
612
713use k8s_openapi:: api:: core:: v1:: ConfigMap ;
814use k8s_openapi:: serde_json:: json;
9- use kube:: api:: { Api , ObjectMeta , Patch , PatchParams , PostParams } ;
15+ use kube:: api:: { Api , ObjectMeta , Patch , PatchParams , PostParams } ;
1016use kube:: client:: Client ;
1117
1218pub static BASE_COMMAND : & str = "kubectl" ; // docs: Kubernetes base command
@@ -41,17 +47,11 @@ pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command
4147
4248#[ derive( Debug ) ]
4349pub enum CliError {
44- InstallerError {
45- reason : String ,
46- } ,
50+ InstallerError { reason : String } ,
4751 ClientError ( kube:: Error ) ,
48- UninstallError {
49- reason : String ,
50- } ,
52+ UninstallError { reason : String } ,
5153 AgentError ( tonic_reflection:: server:: Error ) ,
52- MonitoringError {
53- reason : String ,
54- } ,
54+ MonitoringError { reason : String } ,
5555}
5656// docs:
5757// error type conversions
@@ -63,12 +63,14 @@ impl From<kube::Error> for CliError {
6363}
6464impl From < anyhow:: Error > for CliError {
6565 fn from ( e : anyhow:: Error ) -> Self {
66- CliError :: MonitoringError { reason : format ! ( "{}" , e) }
66+ CliError :: MonitoringError {
67+ reason : format ! ( "{}" , e) ,
68+ }
6769 }
6870}
6971impl From < ( ) > for CliError {
70- fn from ( v : ( ) ) -> Self {
71- return ( ) . into ( )
72+ fn from ( v : ( ) ) -> Self {
73+ return ( ) . into ( ) ;
7274 }
7375}
7476
@@ -142,26 +144,137 @@ pub async fn connect_to_client() -> Result<Client, kube::Error> {
142144// Returns an error if the command fails
143145
144146pub fn update_cli ( ) {
147+ let latest_version = get_latest_cfcli_version ( ) . expect ( "Can't get the latest version" ) ;
145148 println ! ( "{} {}" , "=====>" . blue( ) . bold( ) , "Updating CortexFlow CLI" ) ;
146- println ! ( "{} {}" , "=====>" . blue( ) . bold( ) , "Looking for a newer version" ) ;
149+ println ! (
150+ "{} {}" ,
151+ "=====>" . blue( ) . bold( ) ,
152+ "Looking for a newer version \n "
153+ ) ;
147154
148- let output = Command :: new ( "cargo" ) . args ( [ "update" , "cortexflow-cli" ] ) . output ( ) . expect ( "error" ) ;
155+ let output = Command :: new ( "cfcli" )
156+ . args ( [ "--version" ] )
157+ . output ( )
158+ . expect ( "error" ) ;
149159
150160 if !output. status . success ( ) {
151- eprintln ! ( "Error updating CLI : {}" , String :: from_utf8_lossy( & output. stderr) ) ;
161+ eprintln ! (
162+ "Error extracting the version : {}" ,
163+ String :: from_utf8_lossy( & output. stderr)
164+ ) ;
152165 } else {
153- println ! ( "✅ Updated CLI" ) ;
166+ // extract the cli version:
167+ let version = String :: from_utf8_lossy ( & output. stdout )
168+ . split_whitespace ( )
169+ . last ( )
170+ . expect ( "An error occured during the version extraction" )
171+ . to_string ( ) ;
172+
173+ if version == latest_version {
174+ println ! (
175+ "{} {} {} {} {} {}{}" ,
176+ "=====>" . blue( ) . bold( ) ,
177+ "Your version" . green( ) . bold( ) ,
178+ ( & version. to_string( ) ) . green( ) . bold( ) ,
179+ "is already up to date" . green( ) . bold( ) ,
180+ "(latest:" . green( ) . bold( ) ,
181+ ( & latest_version) . green( ) . bold( ) ,
182+ ")\n " . green( ) . bold( )
183+ ) ;
184+ } else {
185+ println ! (
186+ "{} {} {} {} {} {}{}" ,
187+ "=====>" . blue( ) . bold( ) ,
188+ "Your version" . red( ) . bold( ) ,
189+ ( & version. to_string( ) ) . red( ) . bold( ) ,
190+ "needs to be updated" . red( ) . bold( ) ,
191+ "(latest:" . red( ) . bold( ) ,
192+ ( & latest_version) . red( ) . bold( ) ,
193+ ")\n " . red( ) . bold( )
194+ ) ;
195+ thread:: sleep ( Duration :: from_secs ( 1 ) ) ;
196+ println ! ( "{} {}" , "=====>" . blue( ) . bold( ) , "Updating the CLI..." ) ;
197+ let update_command = Command :: new ( "cargo" )
198+ . args ( [ "install" , "cortexflow-cli" , "--force" ] )
199+ . output ( )
200+ . expect ( "error" ) ;
201+ if !update_command. status . success ( ) {
202+ eprintln ! (
203+ "Error updating the CLI: {} " ,
204+ String :: from_utf8_lossy( & update_command. stderr)
205+ ) ;
206+ } else {
207+ println ! (
208+ "{} {}" ,
209+ "=====>" . blue( ) . bold( ) ,
210+ "CLI updated" . green( ) . bold( )
211+ )
212+ }
213+ }
214+ }
215+ }
216+
217+ // docs:
218+ //
219+ // This function returns the latest version of the CLI from the crates.io registry
220+ pub fn get_latest_cfcli_version ( ) -> Result < String , Error > {
221+ let output = Command :: new ( "cargo" )
222+ . args ( [ "search" , "cortexflow-cli" , "--limit" , "1" ] )
223+ . output ( )
224+ . expect ( "Error" ) ;
225+
226+ if !output. status . success ( ) {
227+ return Err ( Error :: msg ( format ! (
228+ "An error occured during the latest version extraction"
229+ ) ) ) ;
230+ } else {
231+ let command_stdout = String :: from_utf8_lossy ( & output. stdout ) ;
232+
233+ // here the data output have this structure:
234+ // cortexflow-cli = "0.1.4" # CortexFlow command line interface made to interact with the CortexBrain core components
235+ // ... and 3 crates more (use --limit N to see more)
236+
237+ // i need to extract only the version tag
238+ let version = extract_version_from_output ( command_stdout) ;
239+
240+ Ok ( version)
154241 }
155242}
156243
244+ // docs:
245+ // this is an helper function used in a unit test
246+ //
247+ // Takes a Clone-On-Write (Cow) smart pointer (the same type returned by the String::from_utf8_lossy(&output.stdout) code )
248+ // and returns a String that contains the cfcli version
249+
250+ fn extract_version_from_output ( command_stdout : Cow < ' _ , str > ) -> String {
251+ let version = command_stdout. split ( '"' ) . nth ( 1 ) . unwrap ( ) . to_string ( ) ;
252+ version
253+ }
254+
157255// docs:
158256//
159257// This is a function to display the CLI Version,Author and Description using a fancy output style
160258
161259pub fn info ( ) {
162- println ! ( "{} {} {}" , "=====>" . blue( ) . bold( ) , "Version:" , env!( "CARGO_PKG_VERSION" ) ) ;
163- println ! ( "{} {} {}" , "=====>" . blue( ) . bold( ) , "Author:" , env!( "CARGO_PKG_AUTHORS" ) ) ;
164- println ! ( "{} {} {}" , "=====>" . blue( ) . bold( ) , "Description:" , env!( "CARGO_PKG_DESCRIPTION" ) ) ;
260+ println ! (
261+ "{} {} {}" ,
262+ "=====>" . blue( ) . bold( ) ,
263+ "Version:" ,
264+ env!( "CARGO_PKG_VERSION" )
265+ ) ;
266+ println ! (
267+ "{} {} {}" ,
268+ "=====>" . blue( ) . bold( ) ,
269+ "Author:" ,
270+ env!( "CARGO_PKG_AUTHORS" )
271+ ) ;
272+ println ! (
273+ "{} {} {}" ,
274+ "=====>" . blue( ) . bold( ) ,
275+ "Description:" ,
276+ env!( "CARGO_PKG_DESCRIPTION" )
277+ ) ;
165278}
166279
167280// docs:
@@ -210,18 +323,12 @@ pub async fn read_configs() -> Result<Vec<String>, CliError> {
210323
211324 Ok ( Vec :: new ( ) ) //in case the key fails
212325 }
213- Err ( _) => {
214- Err (
215- CliError :: ClientError (
216- kube:: Error :: Api ( ErrorResponse {
217- status : "failed" . to_string ( ) ,
218- message : "Failed to connect to kubernetes client" . to_string ( ) ,
219- reason : "Your cluster is probably disconnected" . to_string ( ) ,
220- code : 404 ,
221- } )
222- )
223- )
224- }
326+ Err ( _) => Err ( CliError :: ClientError ( kube:: Error :: Api ( ErrorResponse {
327+ status : "failed" . to_string ( ) ,
328+ message : "Failed to connect to kubernetes client" . to_string ( ) ,
329+ reason : "Your cluster is probably disconnected" . to_string ( ) ,
330+ code : 404 ,
331+ } ) ) ) ,
225332 }
226333}
227334
@@ -271,18 +378,12 @@ pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(),
271378 }
272379 Ok ( ( ) )
273380 }
274- Err ( _) => {
275- Err (
276- CliError :: ClientError (
277- kube:: Error :: Api ( ErrorResponse {
278- status : "failed" . to_string ( ) ,
279- message : "Failed to connect to kubernetes client" . to_string ( ) ,
280- reason : "Your cluster is probably disconnected" . to_string ( ) ,
281- code : 404 ,
282- } )
283- )
284- )
285- }
381+ Err ( _) => Err ( CliError :: ClientError ( kube:: Error :: Api ( ErrorResponse {
382+ status : "failed" . to_string ( ) ,
383+ message : "Failed to connect to kubernetes client" . to_string ( ) ,
384+ reason : "Your cluster is probably disconnected" . to_string ( ) ,
385+ code : 404 ,
386+ } ) ) ) ,
286387 }
287388}
288389
@@ -350,21 +451,20 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), C
350451 let name = "cortexbrain-client-config" ;
351452 let api: Api < ConfigMap > = Api :: namespaced ( client, namespace) ;
352453
353- let blocklist_yaml = config_struct. blocklist
454+ let blocklist_yaml = config_struct
455+ . blocklist
354456 . iter ( )
355457 . map ( |x| format ! ( "{}" , x) )
356458 . collect :: < Vec < String > > ( )
357459 . join ( "\n " ) ;
358460
359- let patch = Patch :: Apply (
360- json ! ( {
361- "apiVersion" : "v1" ,
362- "kind" : "ConfigMap" ,
363- "data" : {
364- "blocklist" : blocklist_yaml
365- }
366- } )
367- ) ;
461+ let patch = Patch :: Apply ( json ! ( {
462+ "apiVersion" : "v1" ,
463+ "kind" : "ConfigMap" ,
464+ "data" : {
465+ "blocklist" : blocklist_yaml
466+ }
467+ } ) ) ;
368468
369469 let patch_params = PatchParams :: apply ( "cortexbrain" ) . force ( ) ;
370470 match api. patch ( name, & patch_params, & patch) . await {
@@ -379,17 +479,28 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), C
379479
380480 Ok ( ( ) )
381481 }
382- Err ( _) => {
383- Err (
384- CliError :: ClientError (
385- kube:: Error :: Api ( ErrorResponse {
386- status : "failed" . to_string ( ) ,
387- message : "Failed to connect to kubernetes client" . to_string ( ) ,
388- reason : "Your cluster is probably disconnected" . to_string ( ) ,
389- code : 404 ,
390- } )
391- )
392- )
393- }
482+ Err ( _) => Err ( CliError :: ClientError ( kube:: Error :: Api ( ErrorResponse {
483+ status : "failed" . to_string ( ) ,
484+ message : "Failed to connect to kubernetes client" . to_string ( ) ,
485+ reason : "Your cluster is probably disconnected" . to_string ( ) ,
486+ code : 404 ,
487+ } ) ) ) ,
488+ }
489+ }
490+
491+ #[ cfg( test) ]
492+ mod tests {
493+ use crate :: essential:: extract_version_from_output;
494+
495+ #[ test]
496+ fn test_version_extraction ( ) {
497+ let command_stdout = String :: from (
498+ r#"cortexflow-cli = "0.1.4-test_123"
499+ # CortexFlow command line interface made to interact with the CortexBrain core components...
500+ and 3 crates more (use --limit N to see more)"# ,
501+ ) ;
502+
503+ let extracted_command = extract_version_from_output ( command_stdout. into ( ) ) ;
504+ assert_eq ! ( extracted_command, "0.1.4-test_123" ) ;
394505 }
395506}
0 commit comments