@@ -120,9 +120,42 @@ async def ssh_connect_and_run_command(
120120 )
121121
122122
123+ def get_hostgroups_help_text (json_file : str ) -> str :
124+ """Get available hostgroups for help text, with warning if config not found."""
125+ try :
126+ json_path = Path (json_file )
127+ if not json_path .exists ():
128+ return f"\n WARNING: Config file not found at { json_file } "
129+
130+ with open (json_path , "r" ) as file :
131+ data = json .load (file )
132+ hostgroups = list (data .keys ())
133+ if hostgroups :
134+ return f"\n Available hostgroups: { ', ' .join (sorted (hostgroups ))} "
135+ else :
136+ return f"\n WARNING: No hostgroups found in { json_file } "
137+ except json .JSONDecodeError :
138+ return f"\n WARNING: Invalid JSON in config file { json_file } "
139+ except Exception :
140+ return f"\n WARNING: Could not read config file { json_file } "
141+
142+
143+ class CustomArgumentParser (argparse .ArgumentParser ):
144+ def error (self , message ):
145+ # Show help with hostgroups when arguments are missing
146+ self .print_help ()
147+ sys .stderr .write (f"\n error: { message } \n " )
148+ sys .exit (2 )
149+
150+
123151async def main () -> int :
124- parser = argparse .ArgumentParser (
125- description = "Run a command on multiple hosts via SSH."
152+ # Set default config path
153+ default_json_file = str (Path .home () / ".config" / "ssh_hosts.json" )
154+
155+ parser = CustomArgumentParser (
156+ description = "Run a command on multiple hosts via SSH." ,
157+ formatter_class = argparse .RawDescriptionHelpFormatter ,
158+ epilog = get_hostgroups_help_text (default_json_file )
126159 )
127160 parser .add_argument (
128161 "--raw" , action = "store_true" , help = "Print raw output without any formatting"
@@ -131,33 +164,44 @@ async def main() -> int:
131164 "--timeout" , type = int , default = 10 , help = "SSH connection timeout in seconds"
132165 )
133166 parser .add_argument (
134- "--json_file " ,
167+ "--json-file " ,
135168 type = str ,
136169 help = "The JSON file containing the list of hosts" ,
137- default = str ( Path . home () / ".config" / "ssh_hosts.json" ) ,
170+ default = default_json_file ,
138171 )
139172 parser .add_argument (
140173 "hostgroup" , type = str , help = "The hostgroup to run the command on"
141174 )
142175 parser .add_argument (
143176 "command" , nargs = argparse .REMAINDER , help = "The command to run on each host"
144177 )
178+
179+ # Handle custom json-file for help text
180+ if "--json-file" in sys .argv :
181+ try :
182+ json_file_idx = sys .argv .index ("--json-file" ) + 1
183+ if json_file_idx < len (sys .argv ) and not sys .argv [json_file_idx ].startswith ("-" ):
184+ custom_json_file = sys .argv [json_file_idx ]
185+ parser .epilog = get_hostgroups_help_text (custom_json_file )
186+ except (IndexError , ValueError ):
187+ pass
188+
145189 args = parser .parse_args ()
146190
147191 setup_logging (args .raw )
148192
149193 try :
150194 json_path = Path (args .json_file )
151195 if not json_path .exists ():
152- raise FileNotFoundError (f"JSON config file not found: { args .json_file } " )
196+ raise FileNotFoundError (f"Config file not found at { args .json_file } " )
153197
154198 with open (json_path , "r" ) as file :
155199 data = json .load (file )
156200 except json .JSONDecodeError as e :
157- logging .error (f"Invalid JSON file: { e } " )
201+ logging .error (f"Invalid JSON in config file { args . json_file } " )
158202 return 1
159203 except Exception as e :
160- logging .error (f"Error reading JSON file: { e } " )
204+ logging .error (f"Could not read config file { args . json_file } " )
161205 return 1
162206
163207 hostgroup = data .get (args .hostgroup )
0 commit comments