1+ import os
2+ import logging
3+
14from cfbs .commands import build_command
25from cf_remote .commands import deploy as deploy_command
36from cf_remote .commands import install as install_command
47from cf_remote .commands import destroy as destroy_command
8+ from cf_remote .remote import run_command , transfer_file
59
10+ from cfengine_cli .utils import UserError
11+ from cfengine_cli .cfengine_wrapper .cfengine_objects import (
12+ Executable ,
13+ _ensure_default_agent_flags ,
14+ )
615from cfengine_cli .cfengine_wrapper .cfengine_utils import (
16+ extract_agent_file ,
17+ prompt_two_options ,
718 require_executable ,
819 require_installation ,
920)
1021
22+ _DEFAULT_CFENGINE_INPUTS_DIR = "/var/cfengine/inputs"
23+
24+
25+ # ---------------------------------------------------------------------------
26+ # File-resolution helpers for run()
27+ # ---------------------------------------------------------------------------
28+
29+
30+ def _remote_path_exists (host : str , path : str ) -> bool :
31+ return run_command (host , f"test -f { path } " , sudo = False ) is not None
32+
33+
34+ def _replace_file_token (command : str , old : str , new : str ) -> str :
35+ tokens = command .split ()
36+ return " " .join (new if t == old else t for t in tokens )
37+
38+
39+ def _resolve_bare_filename_local (command : str , file_arg : str ) -> str :
40+ cwd_path = os .path .join (os .getcwd (), file_arg )
41+ inputs_path = os .path .join (_DEFAULT_CFENGINE_INPUTS_DIR , file_arg )
42+ cwd_exists = os .path .isfile (cwd_path )
43+ inputs_exists = os .path .isfile (inputs_path )
44+
45+ if not cwd_exists and not inputs_exists :
46+ raise UserError (
47+ f"Could not find '{ file_arg } ' in the current directory or in "
48+ f"{ _DEFAULT_CFENGINE_INPUTS_DIR } ."
49+ )
50+
51+ if cwd_exists and inputs_exists :
52+ choice = prompt_two_options (
53+ f"'{ file_arg } ' exists both in the current directory and in "
54+ f"{ _DEFAULT_CFENGINE_INPUTS_DIR } ." ,
55+ f"the copy in the current directory ({ cwd_path } )" ,
56+ f"the copy already in { _DEFAULT_CFENGINE_INPUTS_DIR } ({ inputs_path } )" ,
57+ )
58+ use_cwd = choice != "b"
59+ else :
60+ use_cwd = cwd_exists
61+
62+ return _replace_file_token (command , file_arg , cwd_path ) if use_cwd else command
63+
64+
65+ def _remote_home_dir (location : str ) -> str :
66+ user = location .split ("@" , 1 )[0 ]
67+ return "/root" if user == "root" else f"/home/{ user } "
68+
69+
70+ def _resolve_file_remote (location : str , command : str , file_arg : str ) -> str :
71+ local_exists = os .path .isfile (file_arg )
72+
73+ local_home = os .path .expanduser ("~" )
74+ remote_home = _remote_home_dir (location )
75+
76+ if file_arg .startswith (local_home + os .sep ):
77+ # e.g. "~/update.cf", shell expands to /home/{user}/update.cf
78+ rel = file_arg [len (local_home ) + 1 :]
79+ remote_path = f"{ remote_home } /{ rel } "
80+ elif file_arg .startswith ("/" ):
81+ remote_path = file_arg
82+ else :
83+ remote_path = f"{ _DEFAULT_CFENGINE_INPUTS_DIR } /{ file_arg } "
84+
85+ remote_exists = _remote_path_exists (location , remote_path )
86+
87+ if not local_exists and not remote_exists :
88+ raise UserError (
89+ f"Could not find '{ file_arg } ' locally or on { location } (checked { remote_path } )."
90+ )
91+
92+ if local_exists and remote_exists :
93+ choice = prompt_two_options (
94+ f"'{ file_arg } ' exists both locally and on { location } (at { remote_path } ).\n Use the:" ,
95+ "local copy (uploads it, possibly overwriting the remote copy)" ,
96+ f"copy already on { location } , unchanged" ,
97+ )
98+ elif local_exists :
99+ choice = "a"
100+ else :
101+ choice = "b"
102+
103+ if choice == "b" :
104+ return _replace_file_token (command , file_arg , remote_path )
105+
106+ uploaded_path = f"{ remote_home } /{ os .path .basename (file_arg )} "
107+ logging .warning (f"Uploading { file_arg } to { location } :{ uploaded_path } " )
108+ transfer_file (location , file_arg )
109+ return _replace_file_token (command , file_arg , uploaded_path )
110+
111+
112+ def _resolve_command_for_agent (agent : Executable , command : str ) -> str :
113+ if agent .name != "cf-agent" :
114+ return command
115+
116+ command = _ensure_default_agent_flags (command )
117+ file_arg = extract_agent_file (command )
118+ if not file_arg :
119+ return command
120+
121+ if agent .is_local :
122+ if "/" in file_arg :
123+ return command # no ambiguity for a local target
124+ return _resolve_bare_filename_local (command , file_arg )
125+
126+ return _resolve_file_remote (agent .location , command , file_arg )
127+
128+
129+ # ---------------------------------------------------------------------------
130+ # Commands
131+ # ---------------------------------------------------------------------------
132+
11133
12134def report (target : str | None = None ) -> int : # TODO? ENT-14122
13135 installation = require_installation (target )
@@ -21,9 +143,10 @@ def report(target: str | None = None) -> int: # TODO? ENT-14122
21143
22144def run (* args , target : str | None = None ) -> int :
23145 agent = require_executable ("cf-agent" , target )
24- if args :
25- return agent .run (* args )
26- return agent .run ("-KIf update.cf" , "-KI" )
146+ if not args :
147+ return agent .run ("-KIf update.cf" , "-KI" )
148+ resolved = [_resolve_command_for_agent (agent , command ) for command in args ]
149+ return agent .run (* resolved )
27150
28151
29152def install () -> int : # TODO ENT-14117
0 commit comments