dcos node ssh
This commit is contained in:
@@ -4,6 +4,10 @@ Usage:
|
|||||||
dcos node --info
|
dcos node --info
|
||||||
dcos node [--json]
|
dcos node [--json]
|
||||||
dcos node log [--follow --lines=N --master --slave=<slave-id>]
|
dcos node log [--follow --lines=N --master --slave=<slave-id>]
|
||||||
|
dcos node ssh [--option SSHOPT=VAL ...]
|
||||||
|
[--config-file=<path>]
|
||||||
|
[--user=<user>]
|
||||||
|
(--master | --slave=<slave-id>)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this screen
|
-h, --help Show this screen
|
||||||
@@ -11,11 +15,17 @@ Options:
|
|||||||
--json Print json-formatted nodes
|
--json Print json-formatted nodes
|
||||||
--follow Output data as the file grows
|
--follow Output data as the file grows
|
||||||
--lines=N Output the last N lines [default: 10]
|
--lines=N Output the last N lines [default: 10]
|
||||||
--master Output the leading master's Mesos log
|
--master Access the leading master
|
||||||
--slave=<slave-id> Output this slave's Mesos log
|
--slave=<slave-id> Access the slave with the provided ID
|
||||||
|
--option SSHOPT=VAL SSH option (see `man ssh_config`)
|
||||||
|
--config-file=<path> Path to ssh config file
|
||||||
|
--user=<user> SSH user [default: core]
|
||||||
--version Show version
|
--version Show version
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
from dcos import cmds, emitting, errors, mesos, util
|
from dcos import cmds, emitting, errors, mesos, util
|
||||||
@@ -61,6 +71,12 @@ def _cmds():
|
|||||||
arg_keys=['--follow', '--lines', '--master', '--slave'],
|
arg_keys=['--follow', '--lines', '--master', '--slave'],
|
||||||
function=_log),
|
function=_log),
|
||||||
|
|
||||||
|
cmds.Command(
|
||||||
|
hierarchy=['node', 'ssh'],
|
||||||
|
arg_keys=['--master', '--slave', '--option', '--config-file',
|
||||||
|
'--user'],
|
||||||
|
function=_ssh),
|
||||||
|
|
||||||
cmds.Command(
|
cmds.Command(
|
||||||
hierarchy=['node'],
|
hierarchy=['node'],
|
||||||
arg_keys=['--json'],
|
arg_keys=['--json'],
|
||||||
@@ -80,7 +96,7 @@ def _info():
|
|||||||
|
|
||||||
|
|
||||||
def _list(json_):
|
def _list(json_):
|
||||||
"""List dcos nodes
|
"""List DCOS nodes
|
||||||
|
|
||||||
:param json_: If true, output json.
|
:param json_: If true, output json.
|
||||||
Otherwise, output a human readable table.
|
Otherwise, output a human readable table.
|
||||||
@@ -89,7 +105,7 @@ def _list(json_):
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
client = mesos.MesosClient()
|
client = mesos.DCOSClient()
|
||||||
slaves = client.get_state_summary()['slaves']
|
slaves = client.get_state_summary()['slaves']
|
||||||
if json_:
|
if json_:
|
||||||
emitter.publish(slaves)
|
emitter.publish(slaves)
|
||||||
@@ -148,3 +164,61 @@ def _mesos_files(master, slave_id):
|
|||||||
slave = mesos.get_master().slave(slave_id)
|
slave = mesos.get_master().slave(slave_id)
|
||||||
files.append(mesos.MesosFile('/slave/log', slave=slave))
|
files.append(mesos.MesosFile('/slave/log', slave=slave))
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def _ssh(master, slave, option, config_file, user):
|
||||||
|
"""SSH into a DCOS node. Since only the masters are definitely
|
||||||
|
publicly available, we first ssh into an arbitrary master, then
|
||||||
|
hop to the desired node.
|
||||||
|
|
||||||
|
: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
|
||||||
|
|
||||||
|
"""
|
||||||
|
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` depends on " +
|
||||||
|
"`ssh-agent` so we can safely use your private key to hop " +
|
||||||
|
"between nodes in your cluster. Please run `ssh-agent`, " +
|
||||||
|
"then add your private key with `ssh-add`.")
|
||||||
|
|
||||||
|
master_public_ip = mesos.DCOSClient().metadata()['PUBLIC_IPV4']
|
||||||
|
ssh_options = ' '.join('-o {}'.format(opt) for opt in option)
|
||||||
|
|
||||||
|
if config_file:
|
||||||
|
ssh_config = '-F {}'.format(config_file)
|
||||||
|
else:
|
||||||
|
ssh_config = ''
|
||||||
|
|
||||||
|
if master:
|
||||||
|
host = 'leader.mesos'
|
||||||
|
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 -A -t {0} {1} {2}@{3} ssh -A -t {2}@{4}".format(
|
||||||
|
ssh_options,
|
||||||
|
ssh_config,
|
||||||
|
user,
|
||||||
|
master_public_ip,
|
||||||
|
host)
|
||||||
|
|
||||||
|
return subprocess.call(cmd, shell=True)
|
||||||
|
|||||||
@@ -121,5 +121,5 @@ def _shutdown(service_id):
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mesos.MesosClient().shutdown_framework(service_id)
|
mesos.DCOSClient().shutdown_framework(service_id)
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ def _mesos_files(completed, fltr, path):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# get tasks
|
# get tasks
|
||||||
client = mesos.MesosClient()
|
client = mesos.DCOSClient()
|
||||||
master = mesos.Master(client.get_master_state())
|
master = mesos.Master(client.get_master_state())
|
||||||
tasks = master.tasks(completed=completed, fltr=fltr)
|
tasks = master.tasks(completed=completed, fltr=fltr)
|
||||||
|
|
||||||
|
|||||||
2
cli/tests/data/node/ssh_config
Normal file
2
cli/tests/data/node/ssh_config
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Host *
|
||||||
|
Protocol 0
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import pty
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
import dcos.util as util
|
import dcos.util as util
|
||||||
|
from dcos import mesos
|
||||||
from dcos.util import create_schema
|
from dcos.util import create_schema
|
||||||
|
|
||||||
from ..fixtures.node import slave_fixture
|
from ..fixtures.node import slave_fixture
|
||||||
@@ -15,6 +20,10 @@ Usage:
|
|||||||
dcos node --info
|
dcos node --info
|
||||||
dcos node [--json]
|
dcos node [--json]
|
||||||
dcos node log [--follow --lines=N --master --slave=<slave-id>]
|
dcos node log [--follow --lines=N --master --slave=<slave-id>]
|
||||||
|
dcos node ssh [--option SSHOPT=VAL ...]
|
||||||
|
[--config-file=<path>]
|
||||||
|
[--user=<user>]
|
||||||
|
(--master | --slave=<slave-id>)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this screen
|
-h, --help Show this screen
|
||||||
@@ -22,8 +31,11 @@ Options:
|
|||||||
--json Print json-formatted nodes
|
--json Print json-formatted nodes
|
||||||
--follow Output data as the file grows
|
--follow Output data as the file grows
|
||||||
--lines=N Output the last N lines [default: 10]
|
--lines=N Output the last N lines [default: 10]
|
||||||
--master Output the leading master's Mesos log
|
--master Access the leading master
|
||||||
--slave=<slave-id> Output this slave's Mesos log
|
--slave=<slave-id> Access the slave with the provided ID
|
||||||
|
--option SSHOPT=VAL SSH option (see `man ssh_config`)
|
||||||
|
--config-file=<path> Path to ssh config file
|
||||||
|
--user=<user> SSH user [default: core]
|
||||||
--version Show version
|
--version Show version
|
||||||
"""
|
"""
|
||||||
assert_command(['dcos', 'node', '--help'], stdout=stdout)
|
assert_command(['dcos', 'node', '--help'], stdout=stdout)
|
||||||
@@ -103,13 +115,85 @@ def test_node_log_invalid_lines():
|
|||||||
returncode=1)
|
returncode=1)
|
||||||
|
|
||||||
|
|
||||||
def _node():
|
def test_node_ssh_master():
|
||||||
returncode, stdout, stderr = exec_command(['dcos', 'node', '--json'])
|
_node_ssh(['--master'])
|
||||||
|
|
||||||
assert returncode == 0
|
|
||||||
assert stderr == b''
|
|
||||||
|
|
||||||
return json.loads(stdout.decode('utf-8'))
|
def test_node_ssh_slave():
|
||||||
|
slave_id = mesos.DCOSClient().get_state_summary()['slaves'][0]['id']
|
||||||
|
_node_ssh(['--slave={}'.format(slave_id)])
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_ssh_option():
|
||||||
|
stdout, stderr = _node_ssh_output(
|
||||||
|
['--master', '--option', 'Protocol=0'])
|
||||||
|
assert stdout == b''
|
||||||
|
assert stderr.startswith(b'ignoring bad proto spec')
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_ssh_config_file():
|
||||||
|
stdout, stderr = _node_ssh_output(
|
||||||
|
['--master', '--config-file', 'tests/data/node/ssh_config'])
|
||||||
|
assert stdout == b''
|
||||||
|
assert stderr.startswith(b'ignoring bad proto spec')
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_ssh_user():
|
||||||
|
stdout, stderr = _node_ssh_output(
|
||||||
|
['--master', '--user=bogus', '--option', 'PasswordAuthentication=no'])
|
||||||
|
assert stdout == b''
|
||||||
|
assert stderr.startswith(b'Permission denied')
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_ssh_no_agent():
|
||||||
|
stderr = (b"There is no SSH_AUTH_SOCK env variable, which likely means "
|
||||||
|
b"you aren't running `ssh-agent`. `dcos node ssh` depends on"
|
||||||
|
b" `ssh-agent` so we can safely use your private key to hop "
|
||||||
|
b"between nodes in your cluster. Please run `ssh-agent`, then "
|
||||||
|
b"add your private key with `ssh-add`.\n")
|
||||||
|
assert_command(['dcos', 'node', 'ssh', '--master'],
|
||||||
|
stdout=b'',
|
||||||
|
stderr=stderr,
|
||||||
|
returncode=1)
|
||||||
|
|
||||||
|
|
||||||
|
def _node_ssh_output(args):
|
||||||
|
# ssh must run with stdin attached to a tty
|
||||||
|
master, slave = pty.openpty()
|
||||||
|
|
||||||
|
cmd = ('ssh-agent /bin/bash -c ' +
|
||||||
|
'"ssh-add /host-home/.vagrant.d/insecure_private_key ' +
|
||||||
|
'2> /dev/null && dcos node ssh --option StrictHostKeyChecking=no' +
|
||||||
|
' {}"').format(' '.join(args))
|
||||||
|
proc = subprocess.Popen(cmd,
|
||||||
|
stdin=slave,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
preexec_fn=os.setsid,
|
||||||
|
close_fds=True,
|
||||||
|
shell=True)
|
||||||
|
os.close(slave)
|
||||||
|
|
||||||
|
# wait for the ssh connection
|
||||||
|
time.sleep(8)
|
||||||
|
|
||||||
|
# kill the whole process group
|
||||||
|
os.killpg(os.getpgid(proc.pid), 15)
|
||||||
|
|
||||||
|
os.close(master)
|
||||||
|
return proc.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
def _node_ssh(args):
|
||||||
|
stdout, stderr = _node_ssh_output(args)
|
||||||
|
|
||||||
|
print('SSH STDOUT: {}'.format(stdout.decode('utf-8')))
|
||||||
|
print('SSH STDERR: {}'.format(stderr.decode('utf-8')))
|
||||||
|
|
||||||
|
assert stdout
|
||||||
|
assert ((stderr == b'') or
|
||||||
|
(len(stderr.split('\n')) == 2 and
|
||||||
|
stderr.startswith('Warning: Permanently added')))
|
||||||
|
|
||||||
|
|
||||||
def _get_schema(slave):
|
def _get_schema(slave):
|
||||||
@@ -120,3 +204,12 @@ def _get_schema(slave):
|
|||||||
schema['properties']['attributes']['additionalProperties'] = True
|
schema['properties']['attributes']['additionalProperties'] = True
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
def _node():
|
||||||
|
returncode, stdout, stderr = exec_command(['dcos', 'node', '--json'])
|
||||||
|
|
||||||
|
assert returncode == 0
|
||||||
|
assert stderr == b''
|
||||||
|
|
||||||
|
return json.loads(stdout.decode('utf-8'))
|
||||||
|
|||||||
@@ -252,10 +252,10 @@ def test_log_completed():
|
|||||||
|
|
||||||
def test_log_master_unavailable():
|
def test_log_master_unavailable():
|
||||||
""" Test master's state.json being unavailable """
|
""" Test master's state.json being unavailable """
|
||||||
client = mesos.MesosClient()
|
client = mesos.DCOSClient()
|
||||||
client.get_master_state = _mock_exception()
|
client.get_master_state = _mock_exception()
|
||||||
|
|
||||||
with patch('dcos.mesos.MesosClient', return_value=client):
|
with patch('dcos.mesos.DCOSClient', return_value=client):
|
||||||
args = ['task', 'log', '_']
|
args = ['task', 'log', '_']
|
||||||
assert_mock(main, args, returncode=1, stderr=(b"exception\n"))
|
assert_mock(main, args, returncode=1, stderr=(b"exception\n"))
|
||||||
|
|
||||||
@@ -263,10 +263,10 @@ def test_log_master_unavailable():
|
|||||||
def test_log_slave_unavailable():
|
def test_log_slave_unavailable():
|
||||||
""" Test slave's state.json being unavailable """
|
""" Test slave's state.json being unavailable """
|
||||||
with app(SLEEP1, 'test-app', True):
|
with app(SLEEP1, 'test-app', True):
|
||||||
client = mesos.MesosClient()
|
client = mesos.DCOSClient()
|
||||||
client.get_slave_state = _mock_exception()
|
client.get_slave_state = _mock_exception()
|
||||||
|
|
||||||
with patch('dcos.mesos.MesosClient', return_value=client):
|
with patch('dcos.mesos.DCOSClient', return_value=client):
|
||||||
args = ['task', 'log', 'test-app']
|
args = ['task', 'log', 'test-app']
|
||||||
stderr = (b"""Error accessing slave: exception\n"""
|
stderr = (b"""Error accessing slave: exception\n"""
|
||||||
b"""No matching tasks. Exiting.\n""")
|
b"""No matching tasks. Exiting.\n""")
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ def get_master():
|
|||||||
:rtype: Master
|
:rtype: Master
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return Master(MesosClient().get_master_state())
|
return Master(DCOSClient().get_master_state())
|
||||||
|
|
||||||
|
|
||||||
class MesosClient(object):
|
class DCOSClient(object):
|
||||||
"""Client for communicating with the Mesos master"""
|
"""Client for communicating with DCOS"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
config = util.get_config()
|
config = util.get_config()
|
||||||
@@ -40,8 +40,21 @@ class MesosClient(object):
|
|||||||
else:
|
else:
|
||||||
self._mesos_master_url = mesos_master_url
|
self._mesos_master_url = mesos_master_url
|
||||||
|
|
||||||
|
def get_dcos_url(self, path):
|
||||||
|
""" Create a DCOS URL
|
||||||
|
|
||||||
|
:param path: the path suffix of the URL
|
||||||
|
:type path: str
|
||||||
|
:returns: DCOS URL
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
if self._dcos_url:
|
||||||
|
return urllib.parse.urljoin(self._dcos_url, path)
|
||||||
|
else:
|
||||||
|
raise util.missing_config_exception('core.dcos_url')
|
||||||
|
|
||||||
def master_url(self, path):
|
def master_url(self, path):
|
||||||
""" Create a URL that hits the master
|
""" Create a master URL
|
||||||
|
|
||||||
:param path: the path suffix of the desired URL
|
:param path: the path suffix of the desired URL
|
||||||
:type path: str
|
:type path: str
|
||||||
@@ -55,7 +68,7 @@ class MesosClient(object):
|
|||||||
|
|
||||||
# TODO (mgummelt): this doesn't work with self._mesos_master_url
|
# TODO (mgummelt): this doesn't work with self._mesos_master_url
|
||||||
def slave_url(self, slave_id, path):
|
def slave_url(self, slave_id, path):
|
||||||
""" Create a URL that hits the slave
|
""" Create a slave URL
|
||||||
|
|
||||||
:param slave_id: slave ID
|
:param slave_id: slave ID
|
||||||
:type slave_id: str
|
:type slave_id: str
|
||||||
@@ -180,11 +193,20 @@ class MesosClient(object):
|
|||||||
url = self.master_url('master/shutdown')
|
url = self.master_url('master/shutdown')
|
||||||
http.post(url, data=data)
|
http.post(url, data=data)
|
||||||
|
|
||||||
|
def metadata(self):
|
||||||
|
""" Get /metadata
|
||||||
|
|
||||||
|
:returns: /metadata content
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
url = self.get_dcos_url('metadata')
|
||||||
|
return http.get(url).json()
|
||||||
|
|
||||||
|
|
||||||
class Master(object):
|
class Master(object):
|
||||||
"""Mesos Master Model
|
"""Mesos Master Model
|
||||||
|
|
||||||
:param state: Mesos master state json
|
:param state: Mesos master's state.json
|
||||||
:type state: dict
|
:type state: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -417,7 +439,7 @@ class Slave(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if not self._state:
|
if not self._state:
|
||||||
self._state = MesosClient().get_slave_state(self['id'])
|
self._state = DCOSClient().get_slave_state(self['id'])
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def _framework_dicts(self):
|
def _framework_dicts(self):
|
||||||
@@ -611,7 +633,7 @@ class MesosFile(object):
|
|||||||
:param slave: slave where the file lives
|
:param slave: slave where the file lives
|
||||||
:type slave: Slave | None
|
:type slave: Slave | None
|
||||||
:param mesos_client: client to use for network requests
|
:param mesos_client: client to use for network requests
|
||||||
:type mesos_client: MesosClient | None
|
:type mesos_client: DCOSClient | None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -630,7 +652,7 @@ class MesosFile(object):
|
|||||||
|
|
||||||
self._task = task
|
self._task = task
|
||||||
self._path = path
|
self._path = path
|
||||||
self._mesos_client = mesos_client or MesosClient()
|
self._mesos_client = mesos_client or DCOSClient()
|
||||||
self._cursor = 0
|
self._cursor = 0
|
||||||
|
|
||||||
def size(self):
|
def size(self):
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ def uninstall(package_name, remove_all, app_id, cli, app):
|
|||||||
remove_all,
|
remove_all,
|
||||||
app_id,
|
app_id,
|
||||||
marathon.create_client(),
|
marathon.create_client(),
|
||||||
mesos.MesosClient())
|
mesos.DCOSClient())
|
||||||
|
|
||||||
if num_apps > 0:
|
if num_apps > 0:
|
||||||
uninstalled = True
|
uninstalled = True
|
||||||
@@ -192,7 +192,7 @@ def uninstall_subcommand(distribution_name):
|
|||||||
return subcommand.uninstall(distribution_name)
|
return subcommand.uninstall(distribution_name)
|
||||||
|
|
||||||
|
|
||||||
def uninstall_app(app_name, remove_all, app_id, init_client, mesos_client):
|
def uninstall_app(app_name, remove_all, app_id, init_client, dcos_client):
|
||||||
"""Uninstalls an app.
|
"""Uninstalls an app.
|
||||||
|
|
||||||
:param app_name: The app to uninstall
|
:param app_name: The app to uninstall
|
||||||
@@ -203,8 +203,8 @@ def uninstall_app(app_name, remove_all, app_id, init_client, mesos_client):
|
|||||||
:type app_id: str
|
:type app_id: str
|
||||||
:param init_client: The program to use to run the app
|
:param init_client: The program to use to run the app
|
||||||
:type init_client: object
|
:type init_client: object
|
||||||
:param mesos_client: the mesos client
|
:param dcos_client: the DCOS client
|
||||||
:type mesos_client: dcos.mesos.MesosClient
|
:type dcos_client: dcos.mesos.DCOSClient
|
||||||
:returns: number of apps uninstalled
|
:returns: number of apps uninstalled
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
@@ -245,7 +245,7 @@ def uninstall_app(app_name, remove_all, app_id, init_client, mesos_client):
|
|||||||
if framework_name is not None:
|
if framework_name is not None:
|
||||||
logger.info(
|
logger.info(
|
||||||
'Trying to shutdown framework {}'.format(framework_name))
|
'Trying to shutdown framework {}'.format(framework_name))
|
||||||
frameworks = mesos.Master(mesos_client.get_master_state()) \
|
frameworks = mesos.Master(dcos_client.get_master_state()) \
|
||||||
.frameworks(inactive=True)
|
.frameworks(inactive=True)
|
||||||
|
|
||||||
# Look up all the framework names
|
# Look up all the framework names
|
||||||
@@ -259,7 +259,7 @@ def uninstall_app(app_name, remove_all, app_id, init_client, mesos_client):
|
|||||||
'Found the following frameworks: {}'.format(framework_ids))
|
'Found the following frameworks: {}'.format(framework_ids))
|
||||||
|
|
||||||
if len(framework_ids) == 1:
|
if len(framework_ids) == 1:
|
||||||
mesos_client.shutdown_framework(framework_ids[0])
|
dcos_client.shutdown_framework(framework_ids[0])
|
||||||
elif len(framework_ids) > 1:
|
elif len(framework_ids) > 1:
|
||||||
raise DCOSException(
|
raise DCOSException(
|
||||||
"Unable to shutdown the framework for [{}] because there "
|
"Unable to shutdown the framework for [{}] because there "
|
||||||
|
|||||||
19
dcos/util.py
19
dcos/util.py
@@ -108,6 +108,7 @@ def get_config():
|
|||||||
:rtype: Toml
|
:rtype: Toml
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# avoid circular import
|
||||||
from dcos import config
|
from dcos import config
|
||||||
|
|
||||||
return config.load_from_path(
|
return config.load_from_path(
|
||||||
@@ -128,13 +129,25 @@ def get_config_vals(config, keys):
|
|||||||
|
|
||||||
missing = [key for key in keys if key not in config]
|
missing = [key for key in keys if key not in config]
|
||||||
if missing:
|
if missing:
|
||||||
|
raise missing_config_exception(keys)
|
||||||
|
|
||||||
|
return [config[key] for key in keys]
|
||||||
|
|
||||||
|
|
||||||
|
def missing_config_exception(keys):
|
||||||
|
""" DCOSException for a missing config value
|
||||||
|
|
||||||
|
:param keys: keys in the config dict
|
||||||
|
:type keys: [str]
|
||||||
|
:returns: DCOSException
|
||||||
|
:rtype: DCOSException
|
||||||
|
"""
|
||||||
|
|
||||||
msg = '\n'.join(
|
msg = '\n'.join(
|
||||||
'Missing required config parameter: "{0}".'.format(key) +
|
'Missing required config parameter: "{0}".'.format(key) +
|
||||||
' Please run `dcos config set {0} <value>`.'.format(key)
|
' Please run `dcos config set {0} <value>`.'.format(key)
|
||||||
for key in keys)
|
for key in keys)
|
||||||
raise DCOSException(msg)
|
return DCOSException(msg)
|
||||||
|
|
||||||
return [config[key] for key in keys]
|
|
||||||
|
|
||||||
|
|
||||||
def which(program):
|
def which(program):
|
||||||
|
|||||||
Reference in New Issue
Block a user