11#!/usr/bin/env python3
2- """CLI for splunk-log-analysis skill."""
2+ """CLI for Root-Cause-Analysis skill."""
33
44import argparse
55import json
@@ -46,17 +46,29 @@ def save_step(analysis_dir: Path, step: int, data: dict) -> Path:
4646 return output_path
4747
4848
49- def upload_analysis_to_jumpbox (analysis_dir : Path , config : Config ) -> bool :
50- """Upload analysis directory to Jumpbox in analysis/{job_id}/ with session.json."""
49+ @trace (name = "Upload analysis" , span_type = SpanType .CHAIN if SpanType else None )
50+ def upload_analysis_to_jumpbox (args : argparse .Namespace , config : Config , span = None ) -> int :
51+ """Upload analysis directory to Jumpbox in /tmp/{job_id}/ with session.json."""
52+ analysis_dir = config .analysis_dir / args .job_id
53+
54+ if not analysis_dir .exists ():
55+ error_message = f"No analysis found for job { args .job_id } "
56+ print (error_message )
57+ if span :
58+ span .set_outputs ({"error" : error_message })
59+ return 1
60+
61+ print (f"Uploading analysis for job { args .job_id } ..." )
62+
5163 if not config .jumpbox_uri :
5264 print (" Skipping upload: JUMPBOX_URI not configured" )
53- return False
65+ return 1
5466
5567 # Parse JUMPBOX_URI format: "user@host -p port"
5668 parts = config .jumpbox_uri .split ()
5769 if len (parts ) < 1 :
5870 print (" Error: Invalid JUMPBOX_URI format" )
59- return False
71+ return 1
6072
6173 ssh_target = parts [0 ] # user@host
6274 ssh_port = None
@@ -71,14 +83,14 @@ def upload_analysis_to_jumpbox(analysis_dir: Path, config: Config) -> bool:
7183 pass
7284
7385 session_id = os .environ .get ("CLAUDE_SESSION_ID" , "unknown" )
74- job_id = analysis_dir . name
75- remote_base_dir = "/tmp/analysis "
86+ job_id = args . job_id
87+ remote_base_dir = f "/tmp/{ job_id } "
7688
7789 # Create session.json file locally (will be overwritten if exists)
7890 session_file = analysis_dir / "session.json"
7991 try :
8092 with open (session_file , "w" ) as f :
81- json .dump ({"session_id" : session_id }, f , indent = 2 )
93+ json .dump ({"session_id" : session_id , "job_id" : job_id }, f , indent = 2 )
8294 except Exception as e :
8395 print (f" Warning: Could not create session.json: { e } " )
8496
@@ -98,25 +110,32 @@ def upload_analysis_to_jumpbox(analysis_dir: Path, config: Config) -> bool:
98110 )
99111 except (subprocess .CalledProcessError , subprocess .TimeoutExpired ) as e :
100112 print (f" Error creating remote directory: { e } " )
101- return False
113+ return 1
102114
103- # Upload analysis directory using rsync
115+ # Upload analysis directory contents using rsync
104116 try :
105117 rsync_cmd = ["rsync" ]
106118 if ssh_port :
107119 rsync_cmd .extend (["-e" , f"ssh -p { ssh_port } " ])
108- rsync_cmd .extend (["-az" , "--quiet" , str (analysis_dir ), f"{ ssh_target } :{ remote_base_dir } /" ])
120+ # Add trailing slash to source to copy contents, not the directory itself
121+ rsync_cmd .extend (
122+ ["-az" , "--quiet" , f"{ str (analysis_dir )} /" , f"{ ssh_target } :{ remote_base_dir } /" ]
123+ )
109124
110125 subprocess .run (
111126 rsync_cmd ,
112127 check = True ,
113128 timeout = 60 ,
114129 )
115- print (f" Uploaded to Jumpbox ({ ssh_target } ): { remote_base_dir } /{ job_id } /" )
116- return True
130+ print (f" Uploaded to Jumpbox ({ ssh_target } ): { remote_base_dir } /" )
131+ if span :
132+ span .set_outputs ({"job_id" : job_id , "success" : True })
133+ return 0
117134 except (subprocess .CalledProcessError , subprocess .TimeoutExpired ) as e :
118135 print (f" Error uploading to Jumpbox: { e } " )
119- return False
136+ if span :
137+ span .set_outputs ({"job_id" : job_id , "success" : False , "error" : str (e )})
138+ return 1
120139
121140
122141def get_step_name (step : int ) -> str :
@@ -344,18 +363,13 @@ def cmd_analyze(args: argparse.Namespace, config: Config, span=None) -> int:
344363 print ("-" * 60 )
345364 _print_quick_summary (job_context , splunk_logs , correlation )
346365
347- # Upload analysis to Jumpbox
348- print ("\n [Upload] Uploading analysis to Jumpbox..." )
349- upload_success = upload_analysis_to_jumpbox (analysis_dir , config )
350-
351366 outputs = {
352367 "job_id" : job_id ,
353368 "status" : job_context .get ("status" ),
354369 "correlation_confidence" : corr .get ("confidence" ),
355370 "failed_tasks" : len (job_context .get ("failed_tasks" , [])),
356371 "pods_found" : len (splunk_logs .get ("pods_found" , [])),
357372 "analysis_dir" : str (analysis_dir ),
358- "uploaded_to_jumpbox" : upload_success ,
359373 }
360374 if span :
361375 span .set_outputs (outputs )
@@ -565,6 +579,10 @@ def main() -> int:
565579 status_parser = subparsers .add_parser ("status" , help = "Show analysis status" )
566580 status_parser .add_argument ("job_id" , help = "Job ID to check" )
567581
582+ # upload command
583+ upload_parser = subparsers .add_parser ("upload" , help = "Upload analysis to Jumpbox" )
584+ upload_parser .add_argument ("--job-id" , required = True , help = "Job ID to upload" )
585+
568586 args = parser .parse_args ()
569587
570588 # Load config
@@ -596,6 +614,7 @@ def main() -> int:
596614 "query" : cmd_query ,
597615 "setup" : cmd_setup ,
598616 "status" : cmd_status ,
617+ "upload" : upload_analysis_to_jumpbox ,
599618 }
600619
601620 exit_code = commands [args .command ](args , config , span )
0 commit comments