Ejemplo n.º 1
0
def _deployment_watch(deployment_id, max_count, interval):
    """
    :param deployment_id: the application id
    :type deployment_di: str
    :param max_count: maximum number of polling calls
    :type max_count: str
    :param interval: wait interval in seconds between polling calls
    :type interval: str
    :returns: process return code
    :rtype: int
    """

    if max_count is not None:
        max_count = util.parse_int(max_count)

    interval = 1 if interval is None else util.parse_int(interval)

    client = marathon.create_client()

    count = 0
    while max_count is None or count < max_count:
        deployment = client.get_deployment(deployment_id)

        if deployment is None:
            return 0

        emitter.publish(deployment)
        time.sleep(interval)
        count += 1

    return 0
Ejemplo n.º 2
0
def _log(follow, lines, master, slave):
    """ Prints the contents of master 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 master: whether to print the master log
    :type master: bool
    :param slave: the slave ID to print
    :type slave: str | None
    :returns: process return code
    :rtype: int
    """

    if not (master or slave):
        raise DCOSException('You must choose one of --master or --slave.')

    lines = util.parse_int(lines)

    mesos_files = _mesos_files(master, slave)

    log.log_files(mesos_files, follow, lines)

    return 0
Ejemplo n.º 3
0
def _log(follow, lines, ssh_config_file, service, file_):
    """Prints the contents of the logs for a given service.  The service
    task is located by first identifying the marathon app running a
    framework named `service`.

    :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.  Used for marathon.
    :type ssh_config_file: str | None
    :param service: service name
    :type service: str
    :param file_: file path to read
    :type file_: str
    :returns: process return code
    :rtype: int
    """

    lines = util.parse_int(lines)

    if service == 'marathon':
        if file_:
            raise DCOSException('The <file> argument is invalid for marathon.'
                                ' The systemd journal is always used for the'
                                ' marathon log.')

        return _log_marathon(follow, lines, ssh_config_file)
    else:
        if ssh_config_file:
            raise DCOSException(
                'The `--ssh-config-file` argument is invalid for non-marathon '
                'services. SSH is not used.')
        return _log_service(follow, lines, service, file_)
Ejemplo n.º 4
0
def _unset(name, index):
    """
    :returns: process status
    :rtype: int
    """

    config_path, toml_config = _load_config()
    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))
    elif (isinstance(value, collections.Sequence) and
          not isinstance(value, six.string_types)):
        if index is not None:
            index = util.parse_int(index)

            if index < 0 or index >= len(value):
                raise DCOSException(
                    'Index ({}) is out of bounds - possible values are '
                    'between {} and {}'.format(index, 0, len(value) - 1))

            value.pop(index)
            toml_config[name] = value
    elif index is not None:
        raise DCOSException(
            'Unsetting based on an index is only supported for lists')

    _save_config_file(config_path, toml_config)
    return 0
Ejemplo n.º 5
0
def _log(follow, lines, ssh_config_file, service, file_):
    """Prints the contents of the logs for a given service.  The service
    task is located by first identifying the marathon app running a
    framework named `service`.

    :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.  Used for marathon.
    :type ssh_config_file: str | None
    :param service: service name
    :type service: str
    :param file_: file path to read
    :type file_: str
    :returns: process return code
    :rtype: int
    """

    lines = util.parse_int(lines)

    if service == 'marathon':
        if file_:
            raise DCOSException('The <file> argument is invalid for marathon.'
                                ' The systemd journal is always used for the'
                                ' marathon log.')

        return _log_marathon(follow, lines, ssh_config_file)
    else:
        if ssh_config_file:
            raise DCOSException(
                'The `--ssh-config-file` argument is invalid for non-marathon '
                'services. SSH is not used.')
        return _log_service(follow, lines, service, file_)
Ejemplo n.º 6
0
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
    """

    if task is None:
        fltr = ""
    else:
        fltr = task

    if file_ is None:
        file_ = 'stdout'

    lines = util.parse_int(lines)

    mesos_files = _mesos_files(completed, fltr, file_)
    if not mesos_files:
        raise DCOSException('No matching tasks. Exiting.')
    log.log_files(mesos_files, follow, lines)

    return 0
Ejemplo n.º 7
0
def _calculate_version(client, app_id, version):
    """
    :param client: Marathon client
    :type client: dcos.marathon.Client
    :param app_id: The ID of the application
    :type app_id: str
    :param version: Relative or absolute version or None
    :type version: str
    :returns: The absolute version as an ISO8601 date-time
    :rtype: str
    """

    # First let's try to parse it as a negative integer
    try:
        value = util.parse_int(version)
    except DCOSException:
        logger.exception('Unable to parse version %s', version)
        return version
    else:
        if value < 0:
            value = -1 * value
            # We have a negative value let's ask Marathon for the last
            # abs(value)
            versions = client.get_app_versions(app_id, value + 1)

            if len(versions) <= value:
                # We don't have enough versions. Return an error.
                msg = "Application {!r} only has {!r} version(s)."
                raise DCOSException(msg.format(app_id, len(versions), value))
            else:
                return versions[value]

        else:
            raise DCOSException(
                'Relative versions must be negative: {}'.format(version))
Ejemplo n.º 8
0
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.')

    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
Ejemplo n.º 9
0
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.')

    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('No matching tasks. Exiting.')

    log.log_files(mesos_files, follow, lines)

    return 0
Ejemplo n.º 10
0
    def deployment_watch(self, deployment_id, max_count, interval):
        """
        :param deployment_id: the application id
        :type deployment_id: str
        :param max_count: maximum number of polling calls
        :type max_count: str
        :param interval: wait interval in seconds between polling calls
        :type interval: str
        :returns: process return code
        :rtype: int
        """

        if max_count is not None:
            max_count = util.parse_int(max_count)

        interval = 1 if interval is None else util.parse_int(interval)

        client = self._create_marathon_client()

        count = 0
        while max_count is None or count < max_count:
            deployment = client.get_deployment(deployment_id)

            if deployment is None:
                return 0
            if util.is_windows_platform():
                os.system('cls')
            else:
                if 'TERM' in os.environ:
                    os.system('clear')
            emitter.publish('Deployment update time: '
                            '{} \n'.format(
                                time.strftime("%Y-%m-%d %H:%M:%S",
                                              time.gmtime())))
            emitter.publish(deployment)
            time.sleep(interval)
            count += 1

        return 0
Ejemplo n.º 11
0
def _deployment_watch(deployment_id, max_count, interval):
    """
    :param deployment_id: the application id
    :type deployment_di: str
    :param max_count: maximum number of polling calls
    :type max_count: str
    :param interval: wait interval in seconds between polling calls
    :type interval: str
    :returns: process return code
    :rtype: int
    """

    if max_count is not None:
        max_count = util.parse_int(max_count)

    interval = 1 if interval is None else util.parse_int(interval)

    client = marathon.create_client()

    count = 0
    while max_count is None or count < max_count:
        deployment = client.get_deployment(deployment_id)

        if deployment is None:
            return 0
        if util.is_windows_platform():
            os.system('cls')
        else:
            if 'TERM' in os.environ:
                os.system('clear')
        emitter.publish('Deployment update time: '
                        '{} \n'.format(time.strftime("%Y-%m-%d %H:%M:%S",
                                                     time.gmtime())))
        emitter.publish(deployment)
        time.sleep(interval)
        count += 1

    return 0
Ejemplo n.º 12
0
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):
        raise DCOSException('You must choose one of --leader or --mesos-id.')

    if lines is None:
        lines = 10

    lines = util.parse_int(lines)

    if log.dcos_log_enabled(version=2):
        _dcos_log_v2(follow, lines, leader, slave, component, filters)
        return 0

    if not log.has_journald_capability():
        if component or filters:
            raise DCOSException('--component or --filter is not '
                                'supported by files API')

        # fall back to mesos files API.
        mesos_files = _mesos_files(leader, slave)
        log.log_files(mesos_files, follow, lines)
        return 0

    # dcos-log does not support logs from leader and agent.
    if leader and slave:
        raise DCOSException(
            'You must choose one of --leader or --mesos-id.')

    # if journald logging enabled.
    _dcos_log(follow, lines, leader, slave, component, filters)
    return 0
Ejemplo n.º 13
0
def _unset(name, index):
    """
    :returns: process status
    :rtype: int
    """

    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))
    elif ((isinstance(value, collections.Sequence) and
           not isinstance(value, six.string_types)) and
          index is not None):
        index = util.parse_int(index)

        if not value:
            raise DCOSException(
                'Index ({}) is out of bounds - [{}] is empty'.format(
                    index,
                    name))
        if index < 0 or index >= len(value):
            raise DCOSException(
                'Index ({}) is out of bounds - possible values are '
                'between {} and {}'.format(index, 0, len(value) - 1))

        popped_value = value.pop(index)
        emitter.publish(
            "[{}]: removed element '{}' at index '{}'".format(
                name, popped_value, index))

        toml_config[name] = value
        config.save(toml_config)
        return 0
    elif index is not None:
        raise DCOSException(
            'Unsetting based on an index is only supported for lists')
    else:
        emitter.publish("Removed [{}]".format(name))
        config.save(toml_config)
        return 0
Ejemplo n.º 14
0
def _start(app_id, instances, force):
    """Start a Marathon application.

    :param app_id: the id for the application
    :type app_id: str
    :param instances: the number of instances to start
    :type instances: str
    :param force: whether to override running deployments
    :type force: bool
    :returns: process return code
    :rtype: int
    """

    # Check that the application exists
    client = marathon.create_client()

    desc = client.get_app(app_id)

    if desc['instances'] > 0:
        emitter.publish(
            'Application {!r} already started: {!r} instances.'.format(
                app_id,
                desc['instances']))
        return 1

    # Need to add the 'id' because it is required
    app_json = {'id': app_id}

    # Set instances to 1 if not specified
    if instances is None:
        instances = 1
    else:
        instances = util.parse_int(instances)
        if instances <= 0:
            emitter.publish(
                'The number of instances must be positive: {!r}.'.format(
                    instances))
            return 1

    app_json['instances'] = instances

    deployment = client.update_app(app_id, app_json, force)

    emitter.publish('Created deployment {}'.format(deployment))

    return 0
Ejemplo n.º 15
0
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):
        raise DCOSException('You must choose one of --leader or --mesos-id.')

    if lines is None:
        lines = 10

    lines = util.parse_int(lines)

    # if journald logging is disabled. Read from files API and exit.
    # https://github.com/dcos/dcos/blob/master/gen/calc.py#L151
    if 'journald' not in log.logging_strategy():
        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

    # dcos-log does not support logs from leader and agent.
    if leader and slave:
        raise DCOSException('You must choose one of --leader or --mesos-id.')

    # if journald logging enabled.
    _dcos_log(follow, lines, leader, slave, component, filters)
    return 0
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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):
        raise DCOSException('You must choose one of --leader or --mesos-id.')

    if lines is None:
        lines = 10

    lines = util.parse_int(lines)

    if not log.has_journald_capability():
        if component or filters:
            raise DCOSException('--component or --filter is not '
                                'supported by files API')

        # fall back to mesos files API.
        mesos_files = _mesos_files(leader, slave)
        log.log_files(mesos_files, follow, lines)
        return 0

    # dcos-log does not support logs from leader and agent.
    if leader and slave:
        raise DCOSException(
            'You must choose one of --leader or --mesos-id.')

    # if journald logging enabled.
    _dcos_log(follow, lines, leader, slave, component, filters)
    return 0
Ejemplo n.º 18
0
def _unset(name, index):
    """
    :returns: process status
    :rtype: int
    """

    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))
    elif ((isinstance(value, collections.Sequence)
           and not isinstance(value, six.string_types)) and index is not None):
        index = util.parse_int(index)

        if not value:
            raise DCOSException(
                'Index ({}) is out of bounds - [{}] is empty'.format(
                    index, name))
        if index < 0 or index >= len(value):
            raise DCOSException(
                'Index ({}) is out of bounds - possible values are '
                'between {} and {}'.format(index, 0,
                                           len(value) - 1))

        popped_value = value.pop(index)
        emitter.publish("[{}]: removed element '{}' at index '{}'".format(
            name, popped_value, index))

        toml_config[name] = value
        config.save(toml_config)
        return 0
    elif index is not None:
        raise DCOSException(
            'Unsetting based on an index is only supported for lists')
    else:
        emitter.publish("Removed [{}]".format(name))
        config.save(toml_config)
        return 0
Ejemplo n.º 19
0
def _version_list(app_id, max_count):
    """
    :param app_id: the id of the application
    :type app_id: str
    :param max_count: the maximum number of version to fetch and return
    :type max_count: str
    :returns: process return code
    :rtype: int
    """

    if max_count is not None:
        max_count = util.parse_int(max_count)

    client = marathon.create_client()

    versions = client.get_app_versions(app_id, max_count)

    emitter.publish(versions)
    return 0
Ejemplo n.º 20
0
def _log(all_, follow, completed, lines, task, file_):
    """ Tail a file in the task's sandbox.

    :param all_: If True, include all tasks
    :type all_: bool
    :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(fltr=fltr, completed=completed, all_=all_)

    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 journald logging is disabled, read files API and exit.
    if not log.dcos_log_enabled():
        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

    # otherwise
    if file_ in ('stdout', 'stderr'):
        _dcos_log(follow, tasks, lines, file_, completed)
        return 0

    raise DCOSException('Invalid file {}. dcos-log only '
                        'supports stdout/stderr'.format(file_))
    return 1
Ejemplo n.º 21
0
def _log(all_, follow, completed, lines, task, file_):
    """ Tail a file in the task's sandbox.

    :param all_: If True, include all tasks
    :type all_: bool
    :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(fltr=fltr, completed=completed, all_=all_)

    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 journald logging is disabled, read files API and exit.
    if not log.dcos_log_enabled():
        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

    # otherwise
    if file_ in ('stdout', 'stderr'):
        _dcos_log(follow, tasks, lines, file_, completed)
        return 0

    raise DCOSException('Invalid file {}. dcos-log only '
                        'supports stdout/stderr'.format(file_))
    return 1