def _log_service(follow, lines, service, file_): """Prints the contents of the logs for a given service. Used for non-marathon services. :param follow: same as unix tail's -f :type follow: bool :param lines: number of lines to print :type lines: int :param service: service name :type service: str :param file_: file path to read :type file_: str :returns: process return code :rtype: int """ if file_ is None: file_ = 'stdout' task = _get_service_task(service) try: if 'id' not in task: raise DCOSException('Missing `id` in task. {}'.format(task)) task_id = task['id'] task_main._log(follow, False, lines, task_id, file_) return 0 except (DCOSAuthenticationException, DCOSAuthorizationException): raise except DCOSException as e: emitter.publish(DefaultError(e)) emitter.publish(DefaultError('Falling back to files API...')) task = _get_service_task(service) return _log_task(task['id'], follow, lines, file_)
def _ssh(leader, slave, option, config_file, user, master_proxy, proxy_ip, private_ip, command): """SSH into a DC/OS 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 :param proxy_ip: If set, SSH-hop from this IP address :type proxy_ip: str | None :param private_ip: The private IP address of the node we want to SSH to. :type private_ip: str | None :param command: Command to run on the node :type command: str | None :rtype: int :returns: process return code """ dcos_client = mesos.DCOSClient() if leader: host = mesos.MesosDNSClient().leader()[0]['ip'] elif private_ip: host = private_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)) if command is None: command = '' ssh_options = ssh_util.get_ssh_options(config_file, option, user, proxy_ip, master_proxy) cmd = "ssh {0} {1} -- {2}".format(ssh_options, host, command) emitter.publish(DefaultError("Running `{}`".format(cmd))) if not master_proxy and not proxy_ip: emitter.publish( DefaultError("If you are running this command from a separate " "network than DC/OS, consider using " "`--master-proxy` or `--proxy-ip`")) return subprocess.Subproc().call(cmd, shell=True)
def _log_marathon(follow, lines, ssh_config_file): """Prints the contents of the marathon logs. Proxy through the master because marathon only runs on the master. :param follow: same as unix tail's -f :type follow: bool :param lines: number of lines to print :type lines: int :param ssh_config_file: SSH config file. :type ssh_config_file: str | None ;:returns: process return code :rtype: int """ journalctl_args = '' if follow: journalctl_args += '-f ' if lines: journalctl_args += '-n {} '.format(lines) leader_ip = marathon.create_client().get_leader().split(':')[0] service = 'dcos-marathon' ssh_options = ssh_util.get_ssh_options(ssh_config_file, master_proxy=True) cmd = "ssh {0} {1} -- journalctl {2}-u {3}".format(ssh_options, leader_ip, journalctl_args, service) emitter.publish(DefaultError("Running `{}`".format(cmd))) return subprocess.Subproc().call(cmd, shell=True)
def _log_marathon(follow, lines, ssh_config_file): """Prints the contents of the marathon logs. :param follow: same as unix tail's -f :type follow: bool :param lines: number of lines to print :type lines: int :param ssh_config_file: SSH config file. :type ssh_config_file: str | None ;:returns: process return code :rtype: int """ ssh_options = util.get_ssh_options(ssh_config_file, []) journalctl_args = '' if follow: journalctl_args += '-f ' if lines: journalctl_args += '-n {} '.format(lines) leader_ip = marathon.create_client().get_leader().split(':')[0] user_string = 'core@' if ssh_config_file: user_string = '' cmd = ("ssh {0}{1}{2} " + "journalctl {3}-u dcos-marathon").format( ssh_options, user_string, leader_ip, journalctl_args) emitter.publish(DefaultError("Running `{}`".format(cmd))) return subprocess.Subproc().call(cmd, shell=True)
def _log(follow, lines, leader, slave, component, filters): """ 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 :param component: DC/OS component name :type component: string :param filters: a list of filters ["key:value", ...] :type filters: list :returns: process return code :rtype: int """ if not (leader or slave) or (leader and slave): raise DCOSException( 'You must choose one of --leader or --mesos-id.') if lines is None: lines = 10 lines = util.parse_int(lines) try: _dcos_log(follow, lines, leader, slave, component, filters) return 0 except (DCOSAuthenticationException, DCOSAuthorizationException): raise except DCOSException as e: emitter.publish(DefaultError(e)) emitter.publish(DefaultError('Falling back to files API...')) if component or filters: raise DCOSException('--component or --filter is not ' 'supported by files API') # fail back to mesos files API. mesos_files = _mesos_files(leader, slave) log.log_files(mesos_files, follow, lines) return 0
def _set(name, value): """ :returns: process status :rtype: int """ toml, msg = config.set_val(name, value) emitter.publish(DefaultError(msg)) if name == "core.dcos_url" and config.uses_deprecated_config(): notice = ( "Setting-up a cluster through this command is being deprecated. " "To setup the CLI to talk to your cluster, please run " "`dcos cluster setup <dcos_url>`.") emitter.publish(DefaultError(notice)) return 0
def _unset(name): """ :returns: process status :rtype: int """ msg = config.unset(name) emitter.publish(DefaultError(msg)) return 0
def _decommission(mesos_id): try: mesos.DCOSClient().mark_agent_gone(mesos_id) except errors.DCOSException as e: emitter.publish( DefaultError("Couldn't mark agent {} as gone :\n\n{}".format( mesos_id, e))) return 1 emitter.publish("Agent {} has been marked as gone.".format(mesos_id))
def _set(name, value): """ :returns: process status :rtype: int """ toml, msg = config.set_val(name, value) emitter.publish(DefaultError(msg)) return 0
def _cluster_setup(dcos_url): """ Setup a cluster using "cluster" directory instead "global" directory, until we deprecate "global" config command: `dcos config set core.dcos_url x` """ notice = ("This config property is being deprecated. " "To setup the CLI to talk to your cluster, please run " "`dcos cluster setup <dcos_url>`.") emitter.publish(DefaultError(notice)) return setup(dcos_url)
def login(dcos_url, password_str, password_env, password_file, provider, username, key_path): """ :param dcos_url: URL of DC/OS cluster :type dcos_url: str :param password_str: password :type password_str: str :param password_env: name of environment variable with password :type password_env: str :param password_file: path to file with password :type password_file: bool :param provider: name of provider to authentication with :type provider: str :param username: username :type username: str :param key_path: path to file with private key :type param: str :rtype: int """ password = _get_password(password_str, password_env, password_file) if provider is None: if username and password: auth.dcos_uid_password_auth(dcos_url, username, password) elif username and key_path: auth.servicecred_auth(dcos_url, username, key_path) else: try: providers = auth.get_providers() # Let users know if they have non-default providers configured # This is a weak check, we should check default versions per # DC/OS version since defaults will change. jj if len(providers) > 2: msg = ("\nYour cluster has several authentication " "providers enabled. Run `dcos auth " "list-providers` to see all providers and `dcos " "auth login --provider <provider-id>` to " "authenticate with a specific provider\n") emitter.publish(DefaultError(msg)) except DCOSException: pass finally: auth.header_challenge_auth(dcos_url) else: providers = auth.get_providers() if providers.get(provider): _trigger_client_method(provider, providers[provider], username, password, key_path) else: msg = "Provider [{}] not configured on your cluster" raise DCOSException(msg.format(provider)) return 0
def uninstall_app(self, package_name, remove_all, app_id, manager_id): """Uninstalls an app. :param package_name: The package to uninstall :type package_name: str :param remove_all: Whether to remove all instances of the named app :type remove_all: boolean :param app_id: App ID of the app instance to uninstall :type app_id: str :returns: whether uninstall was successful or not :rtype: bool :param manager_id: the custom manager to forward this request to :type manager_id: str """ params = {"packageName": package_name, "managerId": manager_id} if remove_all is True: params["all"] = True if app_id is not None: params["appId"] = app_id response = self.cosmos_post("uninstall", params) results = response.json().get("results") uninstalled_versions = [] for res in results: version = res.get("packageVersion") if version not in uninstalled_versions: emitter.publish( DefaultError( 'Uninstalled package [{}] version [{}]'.format( res.get("packageName"), res.get("packageVersion")))) uninstalled_versions += [res.get("packageVersion")] if res.get("postUninstallNotes") is not None: emitter.publish(DefaultError( res.get("postUninstallNotes"))) return True
def _set(name, value): """ :returns: process status :rtype: int """ if name == "core.dcos_url": return _cluster_setup(value) toml, msg = config.set_val(name, value) emitter.publish(DefaultError(msg)) return 0
def _main(argv): for i, arg in enumerate(argv): if arg == '--show-failures': argv[i] = '--failures' warning = ("'--show-failures' is deprecated, " "please use '--failures' instead.\n") emitter.publish(DefaultError(warning)) args = docopt.docopt(default_doc("job"), argv=argv, version='dcos-job version {}'.format(dcoscli.version)) return cmds.execute(_cmds(), args)
def set_val(name, value): """ :param name: name of paramater :type name: str :param value: value to set to paramater `name` :type param: str :returns: Toml config :rtype: Toml """ toml_config = util.get_config(True) section, subkey = split_key(name) config_schema = get_config_schema(section) new_value = jsonitem.parse_json_value(subkey, value, config_schema) toml_config_pre = copy.deepcopy(toml_config) if section not in toml_config_pre._dictionary: toml_config_pre._dictionary[section] = {} value_exists = name in toml_config old_value = toml_config.get(name) toml_config[name] = new_value check_config(toml_config_pre, toml_config) save(toml_config) msg = "[{}]: ".format(name) if name == "core.dcos_acs_token": if not value_exists: msg += "set" elif old_value == new_value: msg += "already set to that value" else: msg += "changed" elif not value_exists: msg += "set to '{}'".format(new_value) elif old_value == new_value: msg += "already set to '{}'".format(old_value) else: msg += "changed from '{}' to '{}'".format(old_value, new_value) emitter.publish(DefaultError(msg)) return toml_config
def _set(name, value): """ :returns: process status :rtype: int """ if name == "package.sources": notice = ("This config property has been deprecated. " "Please add your repositories with `dcos package repo add`") return DCOSException(notice) if name == "core.email": notice = "This config property has been deprecated." return DCOSException(notice) toml, msg = config.set_val(name, value) emitter.publish(DefaultError(msg)) return 0
def _log_marathon(follow, lines, ssh_config_file): """Prints the contents of the marathon logs. Proxy through the master because marathon only runs on the master. :param follow: same as unix tail's -f :type follow: bool :param lines: number of lines to print :type lines: int :param ssh_config_file: SSH config file. :type ssh_config_file: str | None ;:returns: process return code :rtype: int """ ssh_options = util.get_ssh_options(ssh_config_file, []) journalctl_args = '' if follow: journalctl_args += '-f ' if lines: journalctl_args += '-n {} '.format(lines) leader_ip = marathon.create_client().get_leader().split(':')[0] user_string = 'core@' if ssh_config_file: user_string = '' dcos_client = mesos.DCOSClient() master_public_ip = dcos_client.metadata().get('PUBLIC_IPV4') service = 'dcos-marathon' cmd = "ssh -At {0}{1}{2} ssh -At {0}{1}{3} journalctl {4}-u {5}".format( ssh_options, user_string, master_public_ip, leader_ip, journalctl_args, service) emitter.publish(DefaultError("Running `{}`".format(cmd))) return subprocess.Subproc().call(cmd, shell=True)
def _ssh(master, slave, option, config_file, user): """SSH into a DCOS node using the IP addresses found in master's state.json :param master: True if the user has opted to SSH into the leading master :type master: 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 :rtype: int :returns: process return code """ ssh_options = util.get_ssh_options(config_file, option) if master: host = mesos.MesosDNSClient().hosts('leader.mesos.')[0]['ip'] else: summary = mesos.DCOSClient().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)) cmd = "ssh -t {0}{1}@{2}".format( ssh_options, user, host) emitter.publish(DefaultError("Running `{}`".format(cmd))) return subprocess.call(cmd, shell=True)
def _load_slaves_state(slaves): """Fetch each slave's state.json in parallel, and return the reachable slaves. :param slaves: slaves to fetch :type slaves: [MesosSlave] :returns: MesosSlave objects that were successfully reached :rtype: [MesosSlave] """ reachable_slaves = [] for job, slave in util.stream(lambda slave: slave.state(), slaves): try: job.result() reachable_slaves.append(slave) except DCOSException as e: emitter.publish( DefaultError('Error accessing slave: {0}'.format(e))) return reachable_slaves
def unset(name): """ :param name: name of config value to unset :type name: str :returns: process status :rtype: None """ toml_config = util.get_config(True) toml_config_pre = copy.deepcopy(toml_config) section = name.split(".", 1)[0] if section not in toml_config_pre._dictionary: toml_config_pre._dictionary[section] = {} value = toml_config.pop(name, None) if value is None: raise DCOSException("Property {!r} doesn't exist".format(name)) elif isinstance(value, collections.Mapping): raise DCOSException(_generate_choice_msg(name, value)) else: emitter.publish(DefaultError("Removed [{}]".format(name))) save(toml_config) return
def _output(curr_header, output_header, header, lines): """Prints a sequence of lines. If `header` is different than `curr_header`, first print the header. :param curr_header: most recently printed header :type curr_header: str :param output_header: whether or not to output the header :type output_header: bool :param header: header for `lines` :type header: str :param lines: lines to print :type lines: [str] :returns: `header` :rtype: str """ if lines: if output_header and header != curr_header: emitter.publish('===> {} <==='.format(header)) if lines == ['']: emitter.publish(DefaultError('No logs for this task')) for line in lines: emitter.publish(line) return header
def _log(follow, completed, lines, task, file_): """ Tail a file in the task's sandbox. :param follow: same as unix tail's -f :type follow: bool :param completed: whether to include completed tasks :type completed: bool :param lines: number of lines to print :type lines: int :param task: task pattern to match :type task: str :param file_: file path to read :type file_: str :returns: process return code :rtype: int """ fltr = task if file_ is None: file_ = 'stdout' if lines is None: lines = 10 lines = util.parse_int(lines) # get tasks client = mesos.DCOSClient() master = mesos.Master(client.get_master_state()) tasks = master.tasks(completed=completed, fltr=fltr) if not tasks: if not fltr: raise DCOSException("No tasks found. Exiting.") elif not completed: completed_tasks = master.tasks(completed=True, fltr=fltr) if completed_tasks: msg = 'No running tasks match ID [{}]; however, there '.format( fltr) if len(completed_tasks) > 1: msg += 'are {} matching completed tasks. '.format( len(completed_tasks)) else: msg += 'is 1 matching completed task. ' msg += 'Run with --completed to see these logs.' raise DCOSException(msg) raise DCOSException('No matching tasks. Exiting.') if file_ in ('stdout', 'stderr') and log.dcos_log_enabled(): try: _dcos_log(follow, tasks, lines, file_, completed) return 0 except (DCOSAuthenticationException, DCOSAuthorizationException): raise except DCOSException as e: emitter.publish(DefaultError(e)) emitter.publish(DefaultError('Falling back to files API...')) mesos_files = _mesos_files(tasks, file_, client) if not mesos_files: if fltr is None: msg = "No tasks found. Exiting." else: msg = "No matching tasks. Exiting." raise DCOSException(msg) log.log_files(mesos_files, follow, lines) return 0
def _ssh(leader, slave, option, config_file, user, master_proxy, proxy_ip, command): """SSH into a DC/OS 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 :param proxy_ip: If set, SSH-hop from this IP address :type proxy_ip: str | None :param command: Command to run on the node :type command: str | 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)) if command is None: command = '' master_public_ip = dcos_client.metadata().get('PUBLIC_IPV4') if master_proxy: if not master_public_ip: raise DCOSException(("Cannot use --master-proxy. Failed to find " "'PUBLIC_IPV4' at {}").format( dcos_client.get_dcos_url('metadata'))) proxy_ip = master_public_ip if proxy_ip: 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/--proxy-ip` 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`.") cmd = "ssh -A -t {0}{1}@{2} ssh -A -t {0}{1}@{3} {4}".format( ssh_options, user, proxy_ip, host, command) else: cmd = "ssh -t {0}{1}@{2} {3}".format( ssh_options, user, host, command) emitter.publish(DefaultError("Running `{}`".format(cmd))) if (not master_proxy and not proxy_ip) and master_public_ip: emitter.publish( DefaultError("If you are running this command from a separate " "network than DC/OS, consider using " "`--master-proxy` or `--proxy-ip`")) return subprocess.Subproc().call(cmd, shell=True)
def uninstall_app(app_name, remove_all, app_id, init_client, dcos_client): """Uninstalls an app. :param app_name: The app to uninstall :type app_name: str :param remove_all: Whether to remove all instances of the named app :type remove_all: boolean :param app_id: App ID of the app instance to uninstall :type app_id: str :param init_client: The program to use to run the app :type init_client: object :param dcos_client: the DCOS client :type dcos_client: dcos.mesos.DCOSClient :returns: number of apps uninstalled :rtype: int """ apps = init_client.get_apps() def is_match(app): encoding = 'utf-8' # We normalize encoding for byte-wise comparison name_label = app.get('labels', {}).get(PACKAGE_NAME_KEY, u'') name_label_enc = name_label.encode(encoding) app_name_enc = app_name.encode(encoding) name_matches = name_label_enc == app_name_enc if app_id is not None: pkg_app_id = app.get('id', '') normalized_app_id = init_client.normalize_app_id(app_id) return name_matches and pkg_app_id == normalized_app_id else: return name_matches matching_apps = [a for a in apps if is_match(a)] if not remove_all and len(matching_apps) > 1: app_ids = [a.get('id') for a in matching_apps] raise DCOSException( ("Multiple apps named [{}] are installed: [{}].\n" + "Please use --app-id to specify the ID of the app to uninstall," + " or use --all to uninstall all apps.").format( app_name, ', '.join(app_ids))) for app in matching_apps: package_json = _decode_and_add_context( app['id'], app.get('labels', {})) # First, remove the app from Marathon init_client.remove_app(app['id'], force=True) # Second, shutdown the framework with Mesos framework_name = app.get('labels', {}).get(PACKAGE_FRAMEWORK_NAME_KEY) if framework_name is not None: logger.info( 'Trying to shutdown framework {}'.format(framework_name)) frameworks = mesos.Master(dcos_client.get_master_state()) \ .frameworks(inactive=True) # Look up all the framework names framework_ids = [ framework['id'] for framework in frameworks if framework['name'] == framework_name ] logger.info( 'Found the following frameworks: {}'.format(framework_ids)) # Emit post uninstall notes emitter.publish( DefaultError( 'Uninstalled package [{}] version [{}]'.format( package_json['name'], package_json['version']))) if 'postUninstallNotes' in package_json: emitter.publish( DefaultError(package_json['postUninstallNotes'])) if len(framework_ids) == 1: dcos_client.shutdown_framework(framework_ids[0]) elif len(framework_ids) > 1: raise DCOSException( "Unable to shutdown the framework for [{}] because there " "are multiple frameworks with the same name: [{}]. " "Manually shut them down using 'dcos service " "shutdown'.".format( framework_name, ', '.join(framework_ids))) return len(matching_apps)
def signal_handler(signal, frame): emitter.publish(DefaultError("User interrupted command with Ctrl-C")) sys.exit(0)
def setup(dcos_url, insecure=False, no_check=False, ca_certs=None, password_str=None, password_env=None, password_file=None, provider=None, username=None, key_path=None): """ Setup the CLI to talk to your DC/OS cluster. :param dcos_url: master ip of cluster :type dcos_url: str :param insecure: whether or not to verify ssl certs :type insecure: bool :param no_check: whether or not to verify downloaded ca cert :type no_check: bool :param ca_certs: path to root CA to verify requests :type ca_certs: str :param password_str: password :type password_str: str :param password_env: name of environment variable with password :type password_env: str :param password_file: path to file with password :type password_file: bool :param provider: name of provider to authentication with :type provider: str :param username: username :type username: str :param key_path: path to file with private key :type param: str :returns: process status :rtype: int """ with cluster.setup_directory() as temp_path: # set cluster as attached cluster.set_attached(temp_path) # Make sure to ignore any environment variable for the DCOS URL. # There is already a mandatory command argument for this. env_warning = ("Ignoring '{}' environment variable.\n") if "DCOS_URL" in os.environ: emitter.publish(DefaultError(env_warning.format('DCOS_URL'))) del os.environ["DCOS_URL"] if "DCOS_DCOS_URL" in os.environ: emitter.publish(DefaultError(env_warning.format('DCOS_DCOS_URL'))) del os.environ["DCOS_DCOS_URL"] # authenticate config.set_val("core.dcos_url", dcos_url) # get validated dcos_url dcos_url = config.get_config_val("core.dcos_url") # configure ssl settings stored_cert = False if insecure: config.set_val("core.ssl_verify", "false") elif ca_certs: config.set_val("core.ssl_verify", ca_certs) elif _needs_cluster_cert(dcos_url): cert = cluster.get_cluster_cert(dcos_url) if cert: stored_cert = _store_cluster_cert(cert, no_check) else: config.set_val("core.ssl_verify", "false") else: config.set_val("core.ssl_verify", "false") try: login(dcos_url, password_str, password_env, password_file, provider, username, key_path) except DCOSAuthenticationException: msg = ("Authentication failed. " "Please run `dcos cluster setup <dcos_url>`") raise DCOSException(msg) # configure cluster directory cluster.setup_cluster_config(dcos_url, temp_path, stored_cert) return 0
def signal_handler(signal, frame): emitter.publish(DefaultError(" User interrupted command. Exiting...")) sys.exit(130)
def _install(package_name, package_version, options_path, cli, global_, app, yes): """Install the specified package. :param package_name: the package to install :type package_name: str :param package_version: package version to install :type package_version: str :param options_path: path to file containing option values :type options_path: str :param cli: indicates if the cli should be installed :type cli: bool :param global_: indicates that the cli should be installed globally :type global_: bool :param app: indicate if the application should be installed :type app: bool :param yes: automatically assume yes to all prompts :type yes: bool :returns: process status :rtype: int """ if global_: msg = 'Usage of --global is deprecated and will be removed in 1.12.' emitter.publish(DefaultError(msg)) if cli is False and app is False: # Install both if neither flag is specified cli = app = True # Fail early if options file isn't valid user_options = util.read_file_json(options_path) package_manager = get_package_manager() pkg = package_manager.get_package_version(package_name, package_version) pkg_json = pkg.package_json() selected = pkg_json.get('selected') if selected: link = ('https://mesosphere.com/' 'catalog-terms-conditions/#certified-services') else: link = ('https://mesosphere.com/' 'catalog-terms-conditions/#community-services') emitter.publish(('By Deploying, you agree to ' 'the Terms and Conditions ' + link)) pre_install_notes = pkg_json.get('preInstallNotes') if app and pre_install_notes: emitter.publish(pre_install_notes) if not confirm('Continue installing?', yes): emitter.publish('Exiting installation.') return 1 if app and pkg.marathon_template(): # Even though package installation will check for template rendering # errors, we want to fail early, before trying to install. pkg.options(user_options) # Install in Marathon msg = 'Installing Marathon app for package [{}] version [{}]'.format( pkg.name(), pkg.version()) emitter.publish(msg) package_manager.install_app(pkg, user_options) if cli and pkg.cli_definition(): # Install subcommand msg = 'Installing CLI subcommand for package [{}] version [{}]'.format( pkg.name(), pkg.version()) emitter.publish(msg) subcommand.install(pkg, global_) subcommand_paths = subcommand.get_package_commands(package_name) new_commands = [ os.path.basename(p).replace('-', ' ', 1) for p in subcommand_paths ] if new_commands: commands = ', '.join(new_commands) plural = "s" if len(new_commands) > 1 else "" emitter.publish("New command{} available: dcos {}".format( plural, commands)) post_install_notes = pkg_json.get('postInstallNotes') if app and post_install_notes: emitter.publish(post_install_notes) return 0
def _ssh(leader, slave, option, config_file, user, master_proxy, command, flag=[], print_command=True, short_circuit=False, output=False, output_dst=None, tty=True, raw=False): """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 :param command: Command to run on the node :type command: str | None :param flag: SSH flags :type flag: [str] :param print_command: If True, print the raw SSH command :type print_command: bool :param short_circuit: Only use the first SSH connection made :type short_circuit: bool :param output: If True, return the output of the ssh command :type output: boolean :param output_dst: Where to send the output of SSH :type output_dst: object | None :param tty: If True, have SSH allocate a TTY :type tty: boolean :param raw: If True, return a subprocess.Popen object :type raw: boolean :rtype: int :returns: process return code | str """ ssh_options = util.get_ssh_options(config_file, option) dcos_client = mesos.DCOSClient() flagstr = " ".join(flag) if tty: flagstr += ' -t' else: flagstr += ' -T' 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)) if command is None: command = '' 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 {0} {1}{2}@{3} ssh {0} {1}{2}@{4} {5}" if short_circuit: cmd = "ssh -A {0} {1}{2}@{3} {5}" cmd = cmd.format(flagstr, ssh_options, user, master_public_ip, host, command) else: cmd = "ssh {0} {1}{2}@{3} {4}".format(flagstr, ssh_options, user, host, command) if print_command: 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 DC/OS, consider using " "`--master-proxy`")) cmd = shlex.split(cmd) if output: return subprocess.check_output(cmd) if raw: if output_dst is not None: return subprocess.Popen(cmd, stderr=output_dst, stdout=output_dst) return subprocess.Popen(cmd) if output_dst is not None: return subprocess.call(cmd, stderr=output_dst, stdout=output_dst) return subprocess.call(cmd)