|
30 | 30 |
|
31 | 31 | # importing this causes the 'config' subcommand to be available |
32 | 32 | from jwt.exceptions import PyJWTError |
33 | | -from milc import set_metadata # this function needed to set metadata immediately below |
34 | 33 | from platformdirs import user_cache_path |
35 | 34 | from pygments import highlight |
36 | 35 | from pygments.formatters import Terminal256Formatter |
|
48 | 47 | from .organization import Organization |
49 | 48 | from .utility import DC_PROVIDERS, MUTABLE_NETWORK_RESOURCES, MUTABLE_RESOURCE_ABBREVIATIONS, RESOURCE_ABBREVIATIONS, RESOURCE_STATUS_SYMBOLS, RESOURCES, is_jwt, normalize_caseless, plural, singular |
50 | 49 |
|
| 50 | +from milc import set_metadata # this function needed to set metadata immediately below |
51 | 51 | set_metadata(version=f"v{netfoundry_version}", author="NetFoundry", name="nfctl") # must precend import milc.cli |
52 | 52 | from milc import cli, questions # this uses metadata set above |
53 | 53 | from milc.subcommand import config # this creates the config subcommand |
@@ -196,135 +196,6 @@ def login(cli): |
196 | 196 | cli.echo(token_env) |
197 | 197 |
|
198 | 198 |
|
199 | | -@cli.argument('-u', '--user', help="alternative proxy user, default is logged in username") |
200 | | -@cli.argument('-s', '--ssh', help="alternative ssh executable, default is 'ssh'") |
201 | | -@cli.argument('ziti_args', help=argparse.SUPPRESS, arg_only=True, nargs='*') |
202 | | -@cli.argument('ziti_subcmd', help=argparse.SUPPRESS, arg_only=True) |
203 | | -@cli.subcommand('run ziti CLI', hidden=True) |
204 | | -def ziti(cli): |
205 | | - """Run read-only Ziti CLI commands remotely on controller host.""" |
206 | | - if not cli.config.general.network: |
207 | | - cli.log.error("need --network=ACMENet") |
208 | | - sysexit(1) |
209 | | - |
210 | | - # set up the ziti config cache |
211 | | - network_name_safe = '_'.join(cli.config.general.network.casefold().split()) |
212 | | - cache_dir_path = user_cache_path(appname=cli.prog_name) |
213 | | - try: |
214 | | - # create and correct mode to 0o700 |
215 | | - cache_dir_path.mkdir(mode=S_IRUSR | S_IWUSR | S_IXUSR, parents=True, exist_ok=True) |
216 | | - cache_dir_path.chmod(mode=S_IRUSR | S_IWUSR | S_IXUSR) |
217 | | - except Exception as e: |
218 | | - raise RuntimeError(f"failed to create cache dir '{str(cache_dir_path.resolve())}', got {e}") |
219 | | - else: |
220 | | - cache_dir_stats = stat(cache_dir_path) |
221 | | - logging.debug(f"ziti config cache dir {str(cache_dir_path.resolve())} has mode {filemode(cache_dir_stats.st_mode)}") |
222 | | - ssh_config_file = Path(cache_dir_path / network_name_safe) |
223 | | - logging.debug(f"ziti config file path is computed '{str(ssh_config_file.resolve())}'") |
224 | | - |
225 | | - # verify we can run OpenSSH client `ssh`, optionally an alternative `ssh` executable |
226 | | - if cli.config.login.ssh: |
227 | | - ssh_bin = cli.config.login.ssh |
228 | | - else: |
229 | | - if platform.system() == 'Windows': |
230 | | - ssh_bin = 'ssh.exe' |
231 | | - else: |
232 | | - ssh_bin = 'ssh' |
233 | | - which_ssh = which(ssh_bin) |
234 | | - if which_ssh: |
235 | | - cli.log.debug(f"found ssh executable in {which_ssh}") |
236 | | - else: |
237 | | - cli.log.error(f"missing executable '{ssh_bin}' in PATH: {environ['PATH']}") |
238 | | - sysexit(1) |
239 | | - exec = cli.run([which_ssh, '-V']) |
240 | | - if exec.returncode == 0: |
241 | | - cli.log.debug(f"found '{which_ssh}' version '{exec.stdout}'") |
242 | | - else: |
243 | | - cli.log.error(f"failed to get {which_ssh} version: {exec.stderr}") |
244 | | - sysexit(exec.returncode) |
245 | | - |
246 | | - if cli.config.ziti.user: |
247 | | - proxy_user = cli.config.ziti.user |
248 | | - else: |
249 | | - try: |
250 | | - proxy_user = getuser() |
251 | | - assert proxy_user, f"failed to get logged in username, got {proxy_user}" |
252 | | - except AssertionError: |
253 | | - raise |
254 | | - |
255 | | - ziti_cmd = " /opt/netfoundry/ziti/ziti " |
256 | | - |
257 | | - # if command is 'edge' and verb is 'login' then get a token and cache to ziti ssh config file, ignoring extra args |
258 | | - if cli.args.ziti_subcmd == 'edge' and cli.args.ziti_args[0] == 'login': |
259 | | - if len(cli.args.ziti_args[1:]) > 0: |
260 | | - cli.log.warning(f"ignoring extra args: {' '.join(cli.args.ziti_args[1:])}") |
261 | | - spinner = get_spinner("Logging in to Ziti Controller Edge Management API") |
262 | | - with spinner: |
263 | | - organization, networks = use_organization(spinner=spinner) |
264 | | - network, network_group = use_network( |
265 | | - organization=organization, |
266 | | - group=cli.config.general.network_group, |
267 | | - network_name=cli.config.general.network, |
268 | | - spinner=spinner) |
269 | | - network_controller = network.get_resource_by_id(type="network-controller", id=network.network_controller['id']) |
270 | | - controller_host = network.get_resource_by_id(type="host", id=network_controller['hostId']) |
271 | | - if network_controller.get('domainName') and network_controller['domainName']: |
272 | | - ziti_ctrl_ip = network_controller['domainName'] |
273 | | - else: |
274 | | - ziti_ctrl_ip = controller_host['ipAddress'] |
275 | | - try: |
276 | | - session = network.get_controller_session(network.network_controller['id']) |
277 | | - except: |
278 | | - spinner.fail("experimental ziti login failed") |
279 | | - sysexit(1) |
280 | | - else: |
281 | | - ziti_token = session['sessionToken'] |
282 | | - ssh_config = f""" |
283 | | -Host {ziti_ctrl_ip} |
284 | | - User {controller_host['username']} |
285 | | - ProxyJump {proxy_user}@bastion.{organization.environment}.netfoundry.io |
286 | | - PubKeyAuthentication yes |
287 | | - PasswordAuthentication no |
288 | | - GSSAPIAuthentication no |
289 | | - ControlMaster auto |
290 | | - ControlPersist 9m |
291 | | - ControlPath {str(cache_dir_path.resolve()) + r'/ssh-%u-%C'} |
292 | | -""" |
293 | | -# UserKnownHostsFile /dev/null |
294 | | - try: |
295 | | - with open(ssh_config_file, 'w') as f: |
296 | | - f.write(ssh_config) |
297 | | - except Exception as e: |
298 | | - raise RuntimeError(f"problem writing ssh_config in {str(ssh_config_file.resolve())}, got {e}") |
299 | | - else: |
300 | | - cli.log.debug(f"wrote ssh_config in {str(ssh_config_file.resolve())}") |
301 | | - proxy_jump_cmd = f" {which_ssh} -F '{str(ssh_config_file.resolve())}' {ziti_ctrl_ip} " |
302 | | - ziti_login_cmd = f" edge login 127.0.0.1:{controller_host['port']}/edge/management/v1/ --token {ziti_token} --cert /opt/netfoundry/ziti/ziti-controller/certs/ca.external/certs/intermediate-chain.pem --read-only " |
303 | | - try: |
304 | | - exec = cli.run(shplit(proxy_jump_cmd + ziti_cmd + ziti_login_cmd), capture_output=False) |
305 | | - except Exception as e: |
306 | | - raise RuntimeError(f"problem running ssh command: {proxy_jump_cmd + ziti_cmd + ziti_login_cmd}, got {e}") |
307 | | - else: |
308 | | - if exec.returncode == 0: # if succeeded |
309 | | - spinner.succeed("Login succeeded") |
310 | | - else: |
311 | | - spinner.fail("Login failed") |
312 | | - sysexit(exec.returncode) |
313 | | - |
314 | | - elif ssh_config_file.exists(): |
315 | | - # compute and check network unique signature |
316 | | - # pass-through ziti commands to remote |
317 | | - with open(ssh_config_file, 'r') as f: |
318 | | - for line in f: |
319 | | - if len(line.split()) == 2 and line.split()[0] == "Host": |
320 | | - ziti_ctrl_ip = line.split()[1] |
321 | | - proxy_jump_cmd = f" {which_ssh} -F '{str(ssh_config_file.resolve())}' {ziti_ctrl_ip} " |
322 | | - cli.run(shplit(proxy_jump_cmd + ziti_cmd + cli.args.ziti_subcmd) + cli.args.ziti_args, capture_output=False) |
323 | | - else: |
324 | | - cli.log.error("need login:\n\t nfctl --network=ACMENet ziti edge login") |
325 | | - sysexit(1) |
326 | | - |
327 | | - |
328 | 199 | @cli.subcommand('logout current profile from an organization') |
329 | 200 | def logout(cli): |
330 | 201 | """Logout by deleting the cached token.""" |
@@ -664,9 +535,12 @@ def get(cli, echo: bool = True, embed='all', spinner: object = None): |
664 | 535 | @cli.argument('-a', '--as', dest='accept', arg_only=True, choices=['create'], help="request the as=create alternative form of the resources") |
665 | 536 | @cli.argument('resource_type', arg_only=True, help='type of resource', metavar="RESOURCE_TYPE", choices=[choice for group in [[type, RESOURCES[type].abbreviation] for type in RESOURCES.keys()] for choice in group]) |
666 | 537 | @cli.subcommand(description='find resources as lists') |
667 | | -def list(cli): |
| 538 | +def list(cli, spinner: object = None): |
668 | 539 | """Find resources as lists.""" |
669 | | - spinner = get_spinner("working") |
| 540 | + if not spinner: |
| 541 | + spinner = get_spinner("working") |
| 542 | + else: |
| 543 | + cli.log.debug("got spinner as function param") |
670 | 544 | if RESOURCE_ABBREVIATIONS.get(cli.args.resource_type): |
671 | 545 | cli.args.resource_type = RESOURCE_ABBREVIATIONS[cli.args.resource_type].name |
672 | 546 | if cli.args.accept and not MUTABLE_NETWORK_RESOURCES.get(cli.args.resource_type): # mutable excludes data-centers |
|
0 commit comments