update vanilla cli to be one executable
This commit is contained in:
@@ -6,71 +6,29 @@ import dcoscli
|
|||||||
import docopt
|
import docopt
|
||||||
import rollbar
|
import rollbar
|
||||||
import six
|
import six
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from dcos import http, util
|
from dcos import http, util
|
||||||
from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY,
|
from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY,
|
||||||
SEGMENT_IO_CLI_ERROR_EVENT,
|
SEGMENT_IO_CLI_ERROR_EVENT,
|
||||||
SEGMENT_IO_CLI_EVENT, SEGMENT_IO_WRITE_KEY_PROD,
|
SEGMENT_IO_CLI_EVENT, SEGMENT_IO_WRITE_KEY_PROD,
|
||||||
SEGMENT_URL)
|
SEGMENT_URL)
|
||||||
|
from dcoscli.subcommand import default_doc
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
session_id = uuid.uuid4().hex
|
session_id = uuid.uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
def wait_and_track(subproc, cluster_id):
|
def _track(conf):
|
||||||
"""
|
"""
|
||||||
Run a command and report it to analytics services.
|
Whether or not to send reporting information
|
||||||
|
|
||||||
:param subproc: Subprocess to capture
|
:param conf: dcos config file
|
||||||
:type subproc: Popen
|
:type conf: Toml
|
||||||
:param cluster_id: dcos cluster id to send to segment
|
:returns: whether to send reporting information
|
||||||
:type cluster_id: str
|
:rtype: bool
|
||||||
:returns: exit code of subproc
|
|
||||||
:rtype: int
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rollbar.init(ROLLBAR_SERVER_POST_KEY, 'prod')
|
return dcoscli.version != 'SNAPSHOT' and conf.get('core.reporting', True)
|
||||||
|
|
||||||
conf = util.get_config()
|
|
||||||
report = conf.get('core.reporting', True)
|
|
||||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
||||||
if report:
|
|
||||||
_segment_track_cli(pool, conf, cluster_id)
|
|
||||||
|
|
||||||
exit_code, err = wait_and_capture(subproc)
|
|
||||||
|
|
||||||
# We only want to catch exceptions, not other stderr messages
|
|
||||||
# (such as "task does not exist", so we look for the 'Traceback'
|
|
||||||
# string. This only works for python, so we'll need to revisit
|
|
||||||
# this in the future when we support subcommands written in other
|
|
||||||
# languages.
|
|
||||||
if report and 'Traceback' in err:
|
|
||||||
_track_err(pool, exit_code, err, conf, cluster_id)
|
|
||||||
|
|
||||||
return exit_code
|
|
||||||
|
|
||||||
|
|
||||||
def wait_and_capture(subproc):
|
|
||||||
"""
|
|
||||||
Run a subprocess and capture its stderr.
|
|
||||||
|
|
||||||
:param subproc: Subprocess to capture
|
|
||||||
:type subproc: Popen
|
|
||||||
:returns: exit code of subproc
|
|
||||||
:rtype: int
|
|
||||||
"""
|
|
||||||
|
|
||||||
err = ''
|
|
||||||
while subproc.poll() is None:
|
|
||||||
line = subproc.stderr.readline().decode('utf-8')
|
|
||||||
err += line
|
|
||||||
sys.stderr.write(line)
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
exit_code = subproc.poll()
|
|
||||||
|
|
||||||
return exit_code, err
|
|
||||||
|
|
||||||
|
|
||||||
def _segment_track(event, conf, properties):
|
def _segment_track(event, conf, properties):
|
||||||
@@ -135,7 +93,7 @@ def _segment_request(path, data):
|
|||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
|
|
||||||
def _track_err(pool, exit_code, err, conf, cluster_id):
|
def track_err(pool, exit_code, err, conf, cluster_id):
|
||||||
"""
|
"""
|
||||||
Report error details to analytics services.
|
Report error details to analytics services.
|
||||||
|
|
||||||
@@ -152,13 +110,16 @@ def _track_err(pool, exit_code, err, conf, cluster_id):
|
|||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not _track(conf):
|
||||||
|
return
|
||||||
|
|
||||||
# Segment.io calls are async, but rollbar is not, so for
|
# Segment.io calls are async, but rollbar is not, so for
|
||||||
# parallelism, we must call segment first.
|
# parallelism, we must call segment first.
|
||||||
_segment_track_err(pool, conf, cluster_id, err, exit_code)
|
_segment_track_err(pool, conf, cluster_id, err, exit_code)
|
||||||
_rollbar_track_err(conf, cluster_id, err, exit_code)
|
_rollbar_track_err(conf, cluster_id, err, exit_code)
|
||||||
|
|
||||||
|
|
||||||
def _segment_track_cli(pool, conf, cluster_id):
|
def segment_track_cli(pool, conf, cluster_id):
|
||||||
"""
|
"""
|
||||||
Send segment.io cli event.
|
Send segment.io cli event.
|
||||||
|
|
||||||
@@ -171,6 +132,9 @@ def _segment_track_cli(pool, conf, cluster_id):
|
|||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not _track(conf):
|
||||||
|
return
|
||||||
|
|
||||||
props = _base_properties(conf, cluster_id)
|
props = _base_properties(conf, cluster_id)
|
||||||
pool.submit(_segment_track, SEGMENT_IO_CLI_EVENT, conf, props)
|
pool.submit(_segment_track, SEGMENT_IO_CLI_EVENT, conf, props)
|
||||||
|
|
||||||
@@ -213,6 +177,8 @@ def _rollbar_track_err(conf, cluster_id, err, exit_code):
|
|||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
rollbar.init(ROLLBAR_SERVER_POST_KEY, 'prod')
|
||||||
|
|
||||||
props = _base_properties(conf, cluster_id)
|
props = _base_properties(conf, cluster_id)
|
||||||
props['exit_code'] = exit_code
|
props['exit_code'] = exit_code
|
||||||
|
|
||||||
@@ -236,13 +202,10 @@ def _command():
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# avoid circular import
|
args = docopt.docopt(default_doc("dcos"),
|
||||||
import dcoscli.main
|
|
||||||
|
|
||||||
args = docopt.docopt(dcoscli.main._doc(),
|
|
||||||
help=False,
|
help=False,
|
||||||
options_first=True)
|
options_first=True)
|
||||||
return args['<command>']
|
return args.get('<command>', "")
|
||||||
|
|
||||||
|
|
||||||
def _base_properties(conf=None, cluster_id=None):
|
def _base_properties(conf=None, cluster_id=None):
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def exec_command(cmd, env=None, stdin=None):
|
|
||||||
"""Execute CLI command
|
|
||||||
|
|
||||||
:param cmd: Program and arguments
|
|
||||||
:type cmd: [str]
|
|
||||||
:param env: Environment variables
|
|
||||||
:type env: dict
|
|
||||||
:param stdin: File to use for stdin
|
|
||||||
:type stdin: file
|
|
||||||
:returns: A tuple with the returncode, stdout and stderr
|
|
||||||
:rtype: (int, bytes, bytes)
|
|
||||||
"""
|
|
||||||
|
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdin=stdin,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
env=env)
|
|
||||||
|
|
||||||
# This is needed to get rid of '\r' from Windows's lines endings.
|
|
||||||
stdout, stderr = [std_stream.replace(b'\r', b'')
|
|
||||||
for std_stream in process.communicate()]
|
|
||||||
|
|
||||||
return (process.returncode, stdout, stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def command_info(doc):
|
|
||||||
"""Get description from doc text
|
|
||||||
:param doc: command help text
|
|
||||||
:type doc: str
|
|
||||||
:returns: one line description of command
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
return doc.split('\n')[1].strip(".").lstrip()
|
|
||||||
@@ -2,31 +2,29 @@ import collections
|
|||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
import pkg_resources
|
|
||||||
from dcos import cmds, config, emitting, http, util
|
from dcos import cmds, config, emitting, http, util
|
||||||
from dcos.errors import DCOSException
|
from dcos.errors import DCOSException
|
||||||
from dcoscli import analytics
|
from dcoscli import analytics
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import default_command_info, default_doc
|
||||||
from dcoscli.main import decorate_docopt_usage
|
from dcoscli.util import decorate_docopt_usage
|
||||||
|
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("config"),
|
||||||
|
argv=argv,
|
||||||
version='dcos-config version {}'.format(dcoscli.version))
|
version='dcos-config version {}'.format(dcoscli.version))
|
||||||
|
|
||||||
http.silence_requests_warnings()
|
http.silence_requests_warnings()
|
||||||
@@ -34,15 +32,6 @@ def _main():
|
|||||||
return cmds.execute(_cmds(), args)
|
return cmds.execute(_cmds(), args)
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/config.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _cmds():
|
def _cmds():
|
||||||
"""
|
"""
|
||||||
:returns: all the supported commands
|
:returns: all the supported commands
|
||||||
@@ -85,7 +74,7 @@ def _info(info):
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("config"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,45 +2,35 @@ import subprocess
|
|||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
import pkg_resources
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from dcos import cmds, emitting, options, subcommand, util
|
from dcos import cmds, emitting, options, subcommand, util
|
||||||
from dcos.errors import DCOSException
|
from dcos.errors import DCOSException
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import (default_command_documentation,
|
||||||
from dcoscli.main import decorate_docopt_usage
|
default_command_info, default_doc)
|
||||||
|
from dcoscli.util import decorate_docopt_usage
|
||||||
|
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("help"),
|
||||||
|
argv=argv,
|
||||||
version='dcos-help version {}'.format(dcoscli.version))
|
version='dcos-help version {}'.format(dcoscli.version))
|
||||||
|
|
||||||
return cmds.execute(_cmds(), args)
|
return cmds.execute(_cmds(), args)
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/help.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _cmds():
|
def _cmds():
|
||||||
"""
|
"""
|
||||||
:returns: All of the supported commands
|
:returns: All of the supported commands
|
||||||
@@ -66,7 +56,7 @@ def _info():
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("help"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -83,9 +73,11 @@ def _help(command):
|
|||||||
else:
|
else:
|
||||||
logger.debug("DCOS bin path: {!r}".format(util.dcos_bin_path()))
|
logger.debug("DCOS bin path: {!r}".format(util.dcos_bin_path()))
|
||||||
|
|
||||||
|
results = [(c, default_command_info(c))
|
||||||
|
for c in subcommand.default_subcommands()]
|
||||||
paths = subcommand.list_paths()
|
paths = subcommand.list_paths()
|
||||||
with ThreadPoolExecutor(max_workers=len(paths)) as executor:
|
with ThreadPoolExecutor(max_workers=len(paths)) as executor:
|
||||||
results = executor.map(subcommand.documentation, paths)
|
results += list(executor.map(subcommand.documentation, paths))
|
||||||
commands_message = options\
|
commands_message = options\
|
||||||
.make_command_summary_string(sorted(results))
|
.make_command_summary_string(sorted(results))
|
||||||
|
|
||||||
@@ -110,5 +102,9 @@ def _help_command(command):
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if command in subcommand.default_subcommands():
|
||||||
|
emitter.publish(default_command_documentation(command))
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
executable = subcommand.command_executables(command)
|
executable = subcommand.command_executables(command)
|
||||||
return subprocess.call([executable, command, '--help'])
|
return subprocess.call([executable, command, '--help'])
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from functools import wraps
|
|
||||||
from subprocess import PIPE, Popen
|
|
||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
import pkg_resources
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from dcos import (auth, constants, emitting, errors, http, mesos, subcommand,
|
from dcos import (auth, constants, emitting, errors, http, mesos, subcommand,
|
||||||
util)
|
util)
|
||||||
from dcos.errors import DCOSAuthenticationException, DCOSException
|
from dcos.errors import DCOSAuthenticationException, DCOSException
|
||||||
from dcoscli import analytics
|
from dcoscli import analytics
|
||||||
|
from dcoscli.subcommand import SubcommandMain, default_doc
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
@@ -28,7 +27,7 @@ def _main():
|
|||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("dcos"),
|
||||||
version='dcos version {}'.format(dcoscli.version),
|
version='dcos version {}'.format(dcoscli.version),
|
||||||
options_first=True)
|
options_first=True)
|
||||||
|
|
||||||
@@ -54,8 +53,6 @@ def _main():
|
|||||||
if not command:
|
if not command:
|
||||||
command = "help"
|
command = "help"
|
||||||
|
|
||||||
executable = subcommand.command_executables(command)
|
|
||||||
|
|
||||||
cluster_id = None
|
cluster_id = None
|
||||||
if dcoscli.version != 'SNAPSHOT' and command and \
|
if dcoscli.version != 'SNAPSHOT' and command and \
|
||||||
command not in ["config", "help"]:
|
command not in ["config", "help"]:
|
||||||
@@ -67,26 +64,28 @@ def _main():
|
|||||||
msg = 'Unable to get the cluster_id of the cluster.'
|
msg = 'Unable to get the cluster_id of the cluster.'
|
||||||
logger.exception(msg)
|
logger.exception(msg)
|
||||||
|
|
||||||
# the call to retrieve cluster_id must happen before we run the subcommand
|
# send args call to segment.io
|
||||||
# so that if you have auth enabled we don't ask for user/pass multiple
|
with ThreadPoolExecutor(max_workers=2) as reporting_executor:
|
||||||
# times (with the text being out of order) before we can cache the auth
|
analytics.segment_track_cli(reporting_executor, config, cluster_id)
|
||||||
# token
|
|
||||||
subproc = Popen([executable, command] + args['<args>'],
|
|
||||||
stderr=PIPE)
|
|
||||||
|
|
||||||
if dcoscli.version != 'SNAPSHOT':
|
# the call to retrieve cluster_id must happen before we run the
|
||||||
return analytics.wait_and_track(subproc, cluster_id)
|
# subcommand so that if you have auth enabled we don't ask for
|
||||||
|
# user/pass multiple times (with the text being out of order)
|
||||||
|
# before we can cache the auth token
|
||||||
|
if command in subcommand.default_subcommands():
|
||||||
|
sc = SubcommandMain(command, args['<args>'])
|
||||||
else:
|
else:
|
||||||
return analytics.wait_and_capture(subproc)[0]
|
executable = subcommand.command_executables(command)
|
||||||
|
sc = subcommand.SubcommandProcess(
|
||||||
|
executable, command, args['<args>'])
|
||||||
|
|
||||||
|
exitcode, err = sc.run_and_capture()
|
||||||
|
|
||||||
def _doc():
|
if err:
|
||||||
"""
|
analytics.track_err(
|
||||||
:rtype: str
|
reporting_executor, exitcode, err, config, cluster_id)
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
return exitcode
|
||||||
'dcoscli',
|
|
||||||
'data/help/dcos.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _config_log_level_environ(log_level):
|
def _config_log_level_environ(log_level):
|
||||||
@@ -115,27 +114,6 @@ def signal_handler(signal, frame):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
def decorate_docopt_usage(func):
|
|
||||||
"""Handle DocoptExit exception
|
|
||||||
|
|
||||||
:param func: function
|
|
||||||
:type func: function
|
|
||||||
:return: wrapped function
|
|
||||||
:rtype: function
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
except docopt.DocoptExit as e:
|
|
||||||
emitter.publish("Command not recognized\n")
|
|
||||||
emitter.publish(e)
|
|
||||||
return 1
|
|
||||||
return result
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def set_ssl_info_env_vars(config):
|
def set_ssl_info_env_vars(config):
|
||||||
"""Set SSL info from config to environment variable if enviornment
|
"""Set SSL info from config to environment variable if enviornment
|
||||||
variable doesn't exist
|
variable doesn't exist
|
||||||
|
|||||||
@@ -9,41 +9,31 @@ import pkg_resources
|
|||||||
from dcos import cmds, emitting, http, jsonitem, marathon, options, util
|
from dcos import cmds, emitting, http, jsonitem, marathon, options, util
|
||||||
from dcos.errors import DCOSException
|
from dcos.errors import DCOSException
|
||||||
from dcoscli import tables
|
from dcoscli import tables
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import default_command_info, default_doc
|
||||||
from dcoscli.main import decorate_docopt_usage
|
from dcoscli.util import decorate_docopt_usage
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("marathon"),
|
||||||
|
argv=argv,
|
||||||
version='dcos-marathon version {}'.format(dcoscli.version))
|
version='dcos-marathon version {}'.format(dcoscli.version))
|
||||||
|
|
||||||
return cmds.execute(_cmds(), args)
|
return cmds.execute(_cmds(), args)
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/marathon.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _cmds():
|
def _cmds():
|
||||||
"""
|
"""
|
||||||
:returns: all the supported commands
|
:returns: all the supported commands
|
||||||
@@ -189,7 +179,8 @@ def _marathon(config_schema, info):
|
|||||||
elif info:
|
elif info:
|
||||||
_info()
|
_info()
|
||||||
else:
|
else:
|
||||||
emitter.publish(options.make_generic_usage_message(_doc()))
|
doc = default_command_info("marathon")
|
||||||
|
emitter.publish(options.make_generic_usage_message(doc))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -201,7 +192,7 @@ def _info():
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("marathon"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,31 +3,29 @@ import subprocess
|
|||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
import pkg_resources
|
|
||||||
from dcos import cmds, emitting, errors, mesos, util
|
from dcos import cmds, emitting, errors, mesos, util
|
||||||
from dcos.errors import DCOSException, DefaultError
|
from dcos.errors import DCOSException, DefaultError
|
||||||
from dcoscli import log, tables
|
from dcoscli import log, tables
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import default_command_info, default_doc
|
||||||
from dcoscli.main import decorate_docopt_usage
|
from dcoscli.util import decorate_docopt_usage
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("node"),
|
||||||
|
argv=argv,
|
||||||
version="dcos-node version {}".format(dcoscli.version))
|
version="dcos-node version {}".format(dcoscli.version))
|
||||||
|
|
||||||
if args.get('--master'):
|
if args.get('--master'):
|
||||||
@@ -42,15 +40,6 @@ def _main():
|
|||||||
return cmds.execute(_cmds(), args)
|
return cmds.execute(_cmds(), args)
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/node.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _cmds():
|
def _cmds():
|
||||||
"""
|
"""
|
||||||
:returns: All of the supported commands
|
:returns: All of the supported commands
|
||||||
@@ -88,7 +77,7 @@ def _info():
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("node"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,34 +13,27 @@ from dcos import (cmds, cosmospackage, emitting, errors, http, options,
|
|||||||
package, subcommand, util)
|
package, subcommand, util)
|
||||||
from dcos.errors import DCOSException
|
from dcos.errors import DCOSException
|
||||||
from dcoscli import tables
|
from dcoscli import tables
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import default_command_info, default_doc
|
||||||
from dcoscli.main import decorate_docopt_usage
|
from dcoscli.util import decorate_docopt_usage
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/package.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("package"),
|
||||||
|
argv=argv,
|
||||||
version='dcos-package version {}'.format(dcoscli.version))
|
version='dcos-package version {}'.format(dcoscli.version))
|
||||||
http.silence_requests_warnings()
|
http.silence_requests_warnings()
|
||||||
|
|
||||||
@@ -127,7 +120,8 @@ def _package(config_schema, info):
|
|||||||
elif info:
|
elif info:
|
||||||
_info()
|
_info()
|
||||||
else:
|
else:
|
||||||
emitter.publish(options.make_generic_usage_message(_doc()))
|
doc = default_doc("package")
|
||||||
|
emitter.publish(options.make_generic_usage_message(doc))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -140,7 +134,7 @@ def _info():
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("package"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,45 +2,34 @@ import subprocess
|
|||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
import pkg_resources
|
|
||||||
from dcos import cmds, emitting, marathon, mesos, util
|
from dcos import cmds, emitting, marathon, mesos, util
|
||||||
from dcos.errors import DCOSException, DefaultError
|
from dcos.errors import DCOSException, DefaultError
|
||||||
from dcoscli import log, tables
|
from dcoscli import log, tables
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import default_command_info, default_doc
|
||||||
from dcoscli.main import decorate_docopt_usage
|
from dcoscli.util import decorate_docopt_usage
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("service"),
|
||||||
|
argv=argv,
|
||||||
version="dcos-service version {}".format(dcoscli.version))
|
version="dcos-service version {}".format(dcoscli.version))
|
||||||
|
|
||||||
return cmds.execute(_cmds(), args)
|
return cmds.execute(_cmds(), args)
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/service.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _cmds():
|
def _cmds():
|
||||||
"""
|
"""
|
||||||
:returns: All of the supported commands
|
:returns: All of the supported commands
|
||||||
@@ -79,7 +68,7 @@ def _info():
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("service"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
99
cli/dcoscli/subcommand.py
Normal file
99
cli/dcoscli/subcommand.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
|
def _default_modules():
|
||||||
|
"""Dict of the default dcos cli subcommands and their main methods
|
||||||
|
|
||||||
|
:returns: default subcommand -> main method
|
||||||
|
:rtype: {}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# avoid circular imports
|
||||||
|
from dcoscli.config import main as config_main
|
||||||
|
from dcoscli.help import main as help_main
|
||||||
|
from dcoscli.marathon import main as marathon_main
|
||||||
|
from dcoscli.node import main as node_main
|
||||||
|
from dcoscli.package import main as package_main
|
||||||
|
from dcoscli.service import main as service_main
|
||||||
|
from dcoscli.task import main as task_main
|
||||||
|
|
||||||
|
return {'config': config_main,
|
||||||
|
'help': help_main,
|
||||||
|
'marathon': marathon_main,
|
||||||
|
'node': node_main,
|
||||||
|
'package': package_main,
|
||||||
|
'service': service_main,
|
||||||
|
'task': task_main
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def default_doc(command):
|
||||||
|
"""Returns documentation of command
|
||||||
|
|
||||||
|
:param command: default DCOS-CLI command
|
||||||
|
:type command: str
|
||||||
|
:returns: config schema of command
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
resource = "data/help/{}.txt".format(command)
|
||||||
|
return pkg_resources.resource_string(
|
||||||
|
'dcoscli',
|
||||||
|
resource).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def default_command_info(command):
|
||||||
|
"""top level documentation of default DCOS-CLI command
|
||||||
|
|
||||||
|
:param command: name of command
|
||||||
|
:param type: str
|
||||||
|
:returns: command summary
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
doc = default_command_documentation(command)
|
||||||
|
return doc.split('\n')[1].strip(".").lstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def default_command_documentation(command):
|
||||||
|
"""documentation of default DCOS-CLI command
|
||||||
|
|
||||||
|
:param command: name of command
|
||||||
|
:param type: str
|
||||||
|
:returns: command summary
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
return default_doc(command).rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
|
class SubcommandMain():
|
||||||
|
|
||||||
|
def __init__(self, command, args):
|
||||||
|
"""Representes a subcommand running in the main thread
|
||||||
|
|
||||||
|
:param commad: function to run in thread
|
||||||
|
:type command: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._command = command
|
||||||
|
self._args = args
|
||||||
|
|
||||||
|
def run_and_capture(self):
|
||||||
|
"""
|
||||||
|
Run a command and capture exceptions. This is a blocking call
|
||||||
|
:returns: tuple of exitcode, error (or None)
|
||||||
|
:rtype: int, str | None
|
||||||
|
"""
|
||||||
|
|
||||||
|
m = _default_modules()[self._command]
|
||||||
|
err = None
|
||||||
|
try:
|
||||||
|
exit_code = m.main([self._command] + self._args)
|
||||||
|
except Exception:
|
||||||
|
err = traceback.format_exc()
|
||||||
|
traceback.print_exc()
|
||||||
|
exit_code = 1
|
||||||
|
return exit_code, err
|
||||||
@@ -2,45 +2,34 @@ import posixpath
|
|||||||
|
|
||||||
import dcoscli
|
import dcoscli
|
||||||
import docopt
|
import docopt
|
||||||
import pkg_resources
|
|
||||||
from dcos import cmds, emitting, mesos, util
|
from dcos import cmds, emitting, mesos, util
|
||||||
from dcos.errors import DCOSException, DCOSHTTPException, DefaultError
|
from dcos.errors import DCOSException, DCOSHTTPException, DefaultError
|
||||||
from dcoscli import log, tables
|
from dcoscli import log, tables
|
||||||
from dcoscli.common import command_info
|
from dcoscli.subcommand import default_command_info, default_doc
|
||||||
from dcoscli.main import decorate_docopt_usage
|
from dcoscli.util import decorate_docopt_usage
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
emitter = emitting.FlatEmitter()
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
return _main()
|
return _main(argv)
|
||||||
except DCOSException as e:
|
except DCOSException as e:
|
||||||
emitter.publish(e)
|
emitter.publish(e)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@decorate_docopt_usage
|
@decorate_docopt_usage
|
||||||
def _main():
|
def _main(argv):
|
||||||
util.configure_process_from_environ()
|
|
||||||
|
|
||||||
args = docopt.docopt(
|
args = docopt.docopt(
|
||||||
_doc(),
|
default_doc("task"),
|
||||||
|
argv=argv,
|
||||||
version="dcos-task version {}".format(dcoscli.version))
|
version="dcos-task version {}".format(dcoscli.version))
|
||||||
|
|
||||||
return cmds.execute(_cmds(), args)
|
return cmds.execute(_cmds(), args)
|
||||||
|
|
||||||
|
|
||||||
def _doc():
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return pkg_resources.resource_string(
|
|
||||||
'dcoscli',
|
|
||||||
'data/help/task.txt').decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _cmds():
|
def _cmds():
|
||||||
"""
|
"""
|
||||||
:returns: All of the supported commands
|
:returns: All of the supported commands
|
||||||
@@ -78,7 +67,7 @@ def _info():
|
|||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emitter.publish(command_info(_doc()))
|
emitter.publish(default_command_info("task"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
27
cli/dcoscli/util.py
Normal file
27
cli/dcoscli/util.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
import docopt
|
||||||
|
from dcos import emitting
|
||||||
|
|
||||||
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
|
def decorate_docopt_usage(func):
|
||||||
|
"""Handle DocoptExit exception
|
||||||
|
|
||||||
|
:param func: function
|
||||||
|
:type func: function
|
||||||
|
:return: wrapped function
|
||||||
|
:rtype: function
|
||||||
|
"""
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
except docopt.DocoptExit as e:
|
||||||
|
emitter.publish("Command not recognized\n")
|
||||||
|
emitter.publish(e)
|
||||||
|
return 1
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
@@ -92,14 +92,7 @@ setup(
|
|||||||
# pip to create the appropriate form of executable for the target platform.
|
# pip to create the appropriate form of executable for the target platform.
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'dcos=dcoscli.main:main',
|
'dcos=dcoscli.main:main'
|
||||||
'dcos-help=dcoscli.help.main:main',
|
|
||||||
'dcos-config=dcoscli.config.main:main',
|
|
||||||
'dcos-marathon=dcoscli.marathon.main:main',
|
|
||||||
'dcos-package=dcoscli.package.main:main',
|
|
||||||
'dcos-service=dcoscli.service.main:main',
|
|
||||||
'dcos-task=dcoscli.task.main:main',
|
|
||||||
'dcos-node=dcoscli.node.main:main'
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ def exec_mock(main, args):
|
|||||||
print('MOCK ARGS: {}'.format(' '.join(args)))
|
print('MOCK ARGS: {}'.format(' '.join(args)))
|
||||||
|
|
||||||
with mock_args(args) as (stdout, stderr):
|
with mock_args(args) as (stdout, stderr):
|
||||||
returncode = main()
|
returncode = main(args)
|
||||||
|
|
||||||
stdout_val = six.b(stdout.getvalue())
|
stdout_val = six.b(stdout.getvalue())
|
||||||
stderr_val = six.b(stderr.getvalue())
|
stderr_val = six.b(stderr.getvalue())
|
||||||
@@ -554,7 +554,7 @@ def ssh_output(cmd):
|
|||||||
proc, master = popen_tty(cmd)
|
proc, master = popen_tty(cmd)
|
||||||
|
|
||||||
# wait for the ssh connection
|
# wait for the ssh connection
|
||||||
time.sleep(8)
|
time.sleep(3)
|
||||||
|
|
||||||
proc.poll()
|
proc.poll()
|
||||||
returncode = proc.returncode
|
returncode = proc.returncode
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ def test_config_set():
|
|||||||
'''Tests that a `dcos config set core.email <email>` makes a
|
'''Tests that a `dcos config set core.email <email>` makes a
|
||||||
segment.io identify call'''
|
segment.io identify call'''
|
||||||
|
|
||||||
args = [util.which('dcos'), 'config', 'set', 'core.email', 'test@mail.com']
|
argv = ['config', 'set', 'core.email', 'test@mail.com']
|
||||||
env = _env_reporting()
|
env = _env_reporting()
|
||||||
|
|
||||||
with patch('sys.argv', args), patch.dict(os.environ, env):
|
with patch.dict(os.environ, env):
|
||||||
assert config_main() == 0
|
assert config_main(argv) == 0
|
||||||
|
|
||||||
# segment.io
|
# segment.io
|
||||||
assert mock_called_some_args(http.post,
|
assert mock_called_some_args(http.post,
|
||||||
@@ -98,12 +98,12 @@ def test_exc():
|
|||||||
with patch('sys.argv', args), \
|
with patch('sys.argv', args), \
|
||||||
patch('dcoscli.version', version), \
|
patch('dcoscli.version', version), \
|
||||||
patch.dict(os.environ, env), \
|
patch.dict(os.environ, env), \
|
||||||
patch('dcoscli.analytics.wait_and_capture',
|
patch('dcoscli.subcommand.SubcommandMain.run_and_capture',
|
||||||
return_value=(1, 'Traceback')), \
|
return_value=(1, "Traceback")), \
|
||||||
patch('dcoscli.analytics._segment_track_cli') as track:
|
patch('dcoscli.analytics._segment_track') as track:
|
||||||
|
|
||||||
assert main() == 1
|
assert main() == 1
|
||||||
assert track.call_count == 1
|
assert track.call_count == 2
|
||||||
assert rollbar.report_message.call_count == 1
|
assert rollbar.report_message.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@@ -118,9 +118,9 @@ def test_config_reporting_false():
|
|||||||
with patch('sys.argv', args), \
|
with patch('sys.argv', args), \
|
||||||
patch('dcoscli.version', version), \
|
patch('dcoscli.version', version), \
|
||||||
patch.dict(os.environ, env), \
|
patch.dict(os.environ, env), \
|
||||||
patch('dcoscli.analytics.wait_and_capture',
|
patch('dcoscli.subcommand.SubcommandMain.run_and_capture',
|
||||||
return_value=(1, 'Traceback')), \
|
return_value=(1, "Traceback")), \
|
||||||
patch('dcoscli.analytics._segment_track_cli') as track:
|
patch('dcoscli.analytics._segment_track') as track:
|
||||||
|
|
||||||
assert main() == 1
|
assert main() == 1
|
||||||
assert track.call_count == 0
|
assert track.call_count == 0
|
||||||
|
|||||||
@@ -180,13 +180,7 @@ def _node_ssh(args):
|
|||||||
stdout, stderr, returncode = _node_ssh_output(args)
|
stdout, stderr, returncode = _node_ssh_output(args)
|
||||||
assert returncode is None
|
assert returncode is None
|
||||||
|
|
||||||
assert stdout
|
|
||||||
assert b"Running `" in stderr
|
assert b"Running `" in stderr
|
||||||
num_lines = len(stderr.decode().split('\n'))
|
|
||||||
expected_num_lines = 2 if '--master-proxy' in args else 3
|
|
||||||
assert (num_lines == expected_num_lines or
|
|
||||||
(num_lines == (expected_num_lines + 1) and
|
|
||||||
b'Warning: Permanently added' in stderr))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_schema(slave):
|
def _get_schema(slave):
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ def get_config_schema(command):
|
|||||||
'data/config-schema/core.json').decode('utf-8'))
|
'data/config-schema/core.json').decode('utf-8'))
|
||||||
|
|
||||||
executable = subcommand.command_executables(command)
|
executable = subcommand.command_executables(command)
|
||||||
return subcommand.config_schema(executable)
|
return subcommand.config_schema(executable, command)
|
||||||
|
|
||||||
|
|
||||||
def check_config(toml_config_pre, toml_config_post):
|
def check_config(toml_config_pre, toml_config_post):
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from subprocess import PIPE, Popen
|
||||||
|
|
||||||
from dcos import constants, util
|
from dcos import constants, emitting, util
|
||||||
from dcos.errors import DCOSException
|
from dcos.errors import DCOSException
|
||||||
|
|
||||||
logger = util.get_logger(__name__)
|
logger = util.get_logger(__name__)
|
||||||
|
emitter = emitting.FlatEmitter()
|
||||||
|
|
||||||
|
|
||||||
def command_executables(subcommand):
|
def command_executables(subcommand):
|
||||||
@@ -20,7 +23,11 @@ def command_executables(subcommand):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
executables = [
|
executables = []
|
||||||
|
if subcommand in default_subcommands():
|
||||||
|
executables += [default_list_paths()]
|
||||||
|
|
||||||
|
executables += [
|
||||||
command_path
|
command_path
|
||||||
for command_path in list_paths()
|
for command_path in list_paths()
|
||||||
if noun(command_path) == subcommand
|
if noun(command_path) == subcommand
|
||||||
@@ -61,6 +68,18 @@ def get_package_commands(package_name):
|
|||||||
return executables
|
return executables
|
||||||
|
|
||||||
|
|
||||||
|
def default_list_paths():
|
||||||
|
"""List the real path to dcos executable
|
||||||
|
|
||||||
|
:returns: list dcos program path
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Let's get all the default subcommands
|
||||||
|
binpath = util.dcos_bin_path()
|
||||||
|
return os.path.join(binpath, "dcos")
|
||||||
|
|
||||||
|
|
||||||
def list_paths():
|
def list_paths():
|
||||||
"""List the real path to executable dcos subcommand programs.
|
"""List the real path to executable dcos subcommand programs.
|
||||||
|
|
||||||
@@ -68,20 +87,11 @@ def list_paths():
|
|||||||
:rtype: [str]
|
:rtype: [str]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Let's get all the default subcommands
|
|
||||||
binpath = util.dcos_bin_path()
|
|
||||||
commands = [
|
|
||||||
os.path.join(binpath, filename)
|
|
||||||
for filename in os.listdir(binpath)
|
|
||||||
if (filename.startswith(constants.DCOS_COMMAND_PREFIX) and
|
|
||||||
_is_executable(os.path.join(binpath, filename)))
|
|
||||||
]
|
|
||||||
|
|
||||||
subcommands = []
|
subcommands = []
|
||||||
for package in distributions():
|
for package in distributions():
|
||||||
subcommands += get_package_commands(package)
|
subcommands += get_package_commands(package)
|
||||||
|
|
||||||
return commands + subcommands
|
return subcommands
|
||||||
|
|
||||||
|
|
||||||
def _is_executable(path):
|
def _is_executable(path):
|
||||||
@@ -118,6 +128,16 @@ def distributions():
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def default_subcommands():
|
||||||
|
"""List the default dcos cli subcommands
|
||||||
|
|
||||||
|
:returns: list of all the default dcos cli subcommands
|
||||||
|
:rtype: [str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
return ["config", "help", "marathon", "node", "package", "service", "task"]
|
||||||
|
|
||||||
|
|
||||||
def documentation(executable_path):
|
def documentation(executable_path):
|
||||||
"""Gather subcommand summary
|
"""Gather subcommand summary
|
||||||
|
|
||||||
@@ -148,17 +168,20 @@ def info(executable_path, path_noun):
|
|||||||
return out.decode('utf-8').strip()
|
return out.decode('utf-8').strip()
|
||||||
|
|
||||||
|
|
||||||
def config_schema(executable_path):
|
def config_schema(executable_path, noun=None):
|
||||||
"""Collects subcommand config schema
|
"""Collects subcommand config schema
|
||||||
|
|
||||||
:param executable_path: real path to the dcos subcommand
|
:param executable_path: real path to the dcos subcommand
|
||||||
:type executable_path: str
|
:type executable_path: str
|
||||||
|
:param noun: name of subcommand
|
||||||
|
:type noun: str
|
||||||
:returns: the subcommand config schema
|
:returns: the subcommand config schema
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
|
if noun is None:
|
||||||
|
noun = noun(executable_path)
|
||||||
out = subprocess.check_output(
|
out = subprocess.check_output(
|
||||||
[executable_path, noun(executable_path), '--config-schema'])
|
[executable_path, noun, '--config-schema'])
|
||||||
|
|
||||||
return json.loads(out.decode('utf-8'))
|
return json.loads(out.decode('utf-8'))
|
||||||
|
|
||||||
@@ -435,3 +458,47 @@ class InstalledSubcommand(object):
|
|||||||
package_json_path = os.path.join(self._dir(), 'package.json')
|
package_json_path = os.path.join(self._dir(), 'package.json')
|
||||||
with util.open_file(package_json_path) as package_json_file:
|
with util.open_file(package_json_path) as package_json_file:
|
||||||
return util.load_json(package_json_file)
|
return util.load_json(package_json_file)
|
||||||
|
|
||||||
|
|
||||||
|
class SubcommandProcess():
|
||||||
|
|
||||||
|
def __init__(self, executable, command, args):
|
||||||
|
"""Representes a subcommand running by a forked process
|
||||||
|
|
||||||
|
:param executable: executable to run
|
||||||
|
:type executable: executable
|
||||||
|
:param command: command to run by executable
|
||||||
|
:type command: str
|
||||||
|
:param args: arguments for command
|
||||||
|
:type args: [str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._executable = executable
|
||||||
|
self._command = command
|
||||||
|
self._args = args
|
||||||
|
|
||||||
|
def run_and_capture(self):
|
||||||
|
"""
|
||||||
|
Run a command and capture exceptions. This is a blocking call
|
||||||
|
:returns: tuple of exitcode, error (or None)
|
||||||
|
:rtype: int, str | None
|
||||||
|
"""
|
||||||
|
|
||||||
|
subproc = Popen([self._executable, self._command] + self._args,
|
||||||
|
stderr=PIPE)
|
||||||
|
err = ''
|
||||||
|
while subproc.poll() is None:
|
||||||
|
line = subproc.stderr.readline().decode('utf-8')
|
||||||
|
err += line
|
||||||
|
sys.stderr.write(line)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
exitcode = subproc.poll()
|
||||||
|
# We only want to catch exceptions, not other stderr messages
|
||||||
|
# (such as "task does not exist", so we look for the 'Traceback'
|
||||||
|
# string. This only works for python, so we'll need to revisit
|
||||||
|
# this in the future when we support subcommands written in other
|
||||||
|
# languages.
|
||||||
|
err = ('Traceback' in err and err) or None
|
||||||
|
|
||||||
|
return exitcode, err
|
||||||
|
|||||||
Reference in New Issue
Block a user