import os import subprocess import dcoscli import docopt from dcos import cmds, emitting, errors, mesos, util from dcos.errors import DCOSException, DefaultError from dcoscli import log, tables from dcoscli.subcommand import default_command_info, default_doc from dcoscli.util import decorate_docopt_usage logger = util.get_logger(__name__) emitter = emitting.FlatEmitter() def main(argv): try: return _main(argv) except DCOSException as e: emitter.publish(e) return 1 @decorate_docopt_usage def _main(argv): args = docopt.docopt( default_doc("node"), argv=argv, version="dcos-node version {}".format(dcoscli.version)) if args.get('--master'): raise DCOSException( '--master has been deprecated. Please use --leader.' ) elif args.get('--slave'): raise DCOSException( '--slave has been deprecated. Please use --mesos-id.' ) return cmds.execute(_cmds(), args) def _cmds(): """ :returns: All of the supported commands :rtype: [Command] """ return [ cmds.Command( hierarchy=['node', '--info'], arg_keys=[], function=_info), cmds.Command( hierarchy=['node', 'log'], arg_keys=['--follow', '--lines', '--leader', '--mesos-id'], function=_log), cmds.Command( hierarchy=['node', 'ssh'], arg_keys=['--leader', '--mesos-id', '--option', '--config-file', '--user', '--master-proxy'], function=_ssh), cmds.Command( hierarchy=['node'], arg_keys=['--json'], function=_list), ] def _info(): """Print node cli information. :returns: process return code :rtype: int """ emitter.publish(default_command_info("node")) return 0 def _list(json_): """List DCOS nodes :param json_: If true, output json. Otherwise, output a human readable table. :type json_: bool :returns: process return code :rtype: int """ client = mesos.DCOSClient() slaves = client.get_state_summary()['slaves'] if json_: emitter.publish(slaves) else: table = tables.slave_table(slaves) output = str(table) if output: emitter.publish(output) else: emitter.publish(errors.DefaultError('No slaves found.')) def _log(follow, lines, leader, slave): """ Prints the contents of leader and slave logs. :param follow: same as unix tail's -f :type follow: bool :param lines: number of lines to print :type lines: int :param leader: whether to print the leading master's log :type leader: bool :param slave: the slave ID to print :type slave: str | None :returns: process return code :rtype: int """ if not (leader or slave): raise DCOSException('You must choose one of --leader or --mesos-id.') if lines is None: lines = 10 lines = util.parse_int(lines) mesos_files = _mesos_files(leader, slave) log.log_files(mesos_files, follow, lines) return 0 def _mesos_files(leader, slave_id): """Returns the MesosFile objects to log :param leader: whether to include the leading master's log file :type leader: bool :param slave_id: the ID of a slave. used to include a slave's log file :type slave_id: str | None :returns: MesosFile objects :rtype: [MesosFile] """ files = [] if leader: files.append(mesos.MesosFile('/master/log')) if slave_id: slave = mesos.get_master().slave(slave_id) files.append(mesos.MesosFile('/slave/log', slave=slave)) return files def _ssh(leader, slave, option, config_file, user, master_proxy): """SSH into a DCOS node using the IP addresses found in master's state.json :param leader: True if the user has opted to SSH into the leading master :type leader: bool | None :param slave: The slave ID if the user has opted to SSH into a slave :type slave: str | None :param option: SSH option :type option: [str] :param config_file: SSH config file :type config_file: str | None :param user: SSH user :type user: str | None :param master_proxy: If True, SSH-hop from a master :type master_proxy: bool | None :rtype: int :returns: process return code """ ssh_options = util.get_ssh_options(config_file, option) dcos_client = mesos.DCOSClient() if leader: host = mesos.MesosDNSClient().hosts('leader.mesos.')[0]['ip'] else: summary = dcos_client.get_state_summary() slave_obj = next((slave_ for slave_ in summary['slaves'] if slave_['id'] == slave), None) if slave_obj: host = mesos.parse_pid(slave_obj['pid'])[1] else: raise DCOSException('No slave found with ID [{}]'.format(slave)) master_public_ip = dcos_client.metadata().get('PUBLIC_IPV4') if master_proxy: if not os.environ.get('SSH_AUTH_SOCK'): raise DCOSException( "There is no SSH_AUTH_SOCK env variable, which likely means " "you aren't running `ssh-agent`. `dcos node ssh " "--master-proxy` depends on `ssh-agent` to safely use your " "private key to hop between nodes in your cluster. Please " "run `ssh-agent`, then add your private key with `ssh-add`.") if not master_public_ip: raise DCOSException(("Cannot use --master-proxy. Failed to find " "'PUBLIC_IPV4' at {}").format( dcos_client.get_dcos_url('metadata'))) cmd = "ssh -A -t {0}{1}@{2} ssh -A -t {1}@{3}".format( ssh_options, user, master_public_ip, host) else: cmd = "ssh -t {0}{1}@{2}".format( ssh_options, user, host) emitter.publish(DefaultError("Running `{}`".format(cmd))) if (not master_proxy) and master_public_ip: emitter.publish( DefaultError("If you are running this command from a separate " "network than DCOS, consider using `--master-proxy`")) return subprocess.call(cmd, shell=True)