saltstack/salt

View on GitHub
salt/modules/at_solaris.py

Summary

Maintainability
F
1 wk
Test Coverage
# -*- coding: utf-8 -*-
'''
Wrapper for at(1) on Solaris-like systems

.. note::
    we try to mirror the generic at module
    where possible

:maintainer:    jorge schrauwen <sjorge@blackdot.be>
:maturity:      new
:platform:      solaris,illumos,smartso

.. versionadded:: 2017.7.0
'''
from __future__ import absolute_import, print_function, unicode_literals

# Import python libs
import re
import time
import datetime
import logging

# Import 3rd-party libs
# pylint: disable=import-error,redefined-builtin
from salt.ext.six.moves import map
from salt.ext import six

# Import salt libs
import salt.utils.files
import salt.utils.path
import salt.utils.platform

log = logging.getLogger(__name__)
__virtualname__ = 'at'


def __virtual__():
    '''
    We only deal with Solaris' specific version of at
    '''
    if not salt.utils.platform.is_sunos():
        return (False, 'The at module could not be loaded: unsupported platform')
    if not salt.utils.path.which('at') or \
        not salt.utils.path.which('atq') or \
        not salt.utils.path.which('atrm'):
        return (False, 'The at module could not be loaded: at command not found')
    return __virtualname__


def atq(tag=None):
    '''
    List all queued and running jobs or only those with
    an optional 'tag'.

    CLI Example:

    .. code-block:: bash

        salt '*' at.atq
        salt '*' at.atq [tag]
        salt '*' at.atq [job number]
    '''
    jobs = []

    res = __salt__['cmd.run_all']('atq')

    if res['retcode'] > 0:
        return {'error': res['stderr']}

    # No jobs so return
    if res['stdout'] == 'no files in queue.':
        return {'jobs': jobs}

    # Jobs created with at.at() will use the following
    # comment to denote a tagged job.
    job_kw_regex = re.compile(r'^### SALT: (\w+)')

    # Split each job into a dictionary and handle
    # pulling out tags or only listing jobs with a certain
    # tag
    for line in res['stdout'].splitlines():
        job_tag = ''

        # skip header
        if line.startswith(' Rank'):
            continue

        # parse job output
        tmp = line.split()
        timestr = ' '.join(tmp[1:5])
        job = tmp[6]
        specs = datetime.datetime(
            *(time.strptime(timestr, '%b %d, %Y %H:%M')[0:5])
        ).isoformat().split('T')
        specs.append(tmp[7])
        specs.append(tmp[5])

        # make sure job is str
        job = six.text_type(job)

        # search for any tags
        atjob_file = '/var/spool/cron/atjobs/{job}'.format(
            job=job
        )
        if __salt__['file.file_exists'](atjob_file):
            with salt.utils.files.fopen(atjob_file, 'r') as atjob:
                for line in atjob:
                    line = salt.utils.stringutils.to_unicode(line)
                    tmp = job_kw_regex.match(line)
                    if tmp:
                        job_tag = tmp.groups()[0]

        # filter on tags
        if not tag:
            jobs.append({'job': job, 'date': specs[0], 'time': specs[1],
                'queue': specs[2], 'user': specs[3], 'tag': job_tag})
        elif tag and tag in [job_tag, job]:
            jobs.append({'job': job, 'date': specs[0], 'time': specs[1],
                'queue': specs[2], 'user': specs[3], 'tag': job_tag})

    return {'jobs': jobs}


def atrm(*args):
    '''
    Remove jobs from the queue.

    CLI Example:

    .. code-block:: bash

        salt '*' at.atrm <jobid> <jobid> .. <jobid>
        salt '*' at.atrm all
        salt '*' at.atrm all [tag]
    '''

    if not args:
        return {'jobs': {'removed': [], 'tag': None}}

    if args[0] == 'all':
        if len(args) > 1:
            opts = list(list(map(str, [j['job'] for j in atq(args[1])['jobs']])))
            ret = {'jobs': {'removed': opts, 'tag': args[1]}}
        else:
            opts = list(list(map(str, [j['job'] for j in atq()['jobs']])))
            ret = {'jobs': {'removed': opts, 'tag': None}}
    else:
        opts = list(list(map(str, [i['job'] for i in atq()['jobs']
            if i['job'] in args])))
        ret = {'jobs': {'removed': opts, 'tag': None}}

    # call atrm for each job in ret['jobs']['removed']
    for job in ret['jobs']['removed']:
        res_job = __salt__['cmd.run_all']('atrm {job}'.format(
            job=job
        ))
        if res_job['retcode'] > 0:
            if 'failed' not in ret['jobs']:
                ret['jobs']['failed'] = {}
            ret['jobs']['failed'][job] = res_job['stderr']

    # remove failed from list
    if 'failed' in ret['jobs']:
        for job in ret['jobs']['failed']:
            ret['jobs']['removed'].remove(job)

    return ret


def at(*args, **kwargs):  # pylint: disable=C0103
    '''
    Add a job to the queue.

    The 'timespec' follows the format documented in the
    at(1) manpage.

    CLI Example:

    .. code-block:: bash

        salt '*' at.at <timespec> <cmd> [tag=<tag>] [runas=<user>]
        salt '*' at.at 12:05am '/sbin/reboot' tag=reboot
        salt '*' at.at '3:05am +3 days' 'bin/myscript' tag=nightly runas=jim
    '''

    # check args
    if len(args) < 2:
        return {'jobs': []}

    # build job
    if 'tag' in kwargs:
        stdin = '### SALT: {0}\n{1}'.format(kwargs['tag'], ' '.join(args[1:]))
    else:
        stdin = ' '.join(args[1:])

    cmd_kwargs = {'stdin': stdin, 'python_shell': False}
    if 'runas' in kwargs:
        cmd_kwargs['runas'] = kwargs['runas']
    res = __salt__['cmd.run_all']('at "{timespec}"'.format(
        timespec=args[0]
    ), **cmd_kwargs)

    # verify job creation
    if res['retcode'] > 0:
        if 'bad time specification' in res['stderr']:
            return {'jobs': [], 'error': 'invalid timespec'}
        return {'jobs': [], 'error': res['stderr']}
    else:
        jobid = res['stderr'].splitlines()[1]
        jobid = six.text_type(jobid.split()[1])
        return atq(jobid)


def atc(jobid):
    '''
    Print the at(1) script that will run for the passed job
    id. This is mostly for debugging so the output will
    just be text.

    CLI Example:

    .. code-block:: bash

        salt '*' at.atc <jobid>
    '''

    atjob_file = '/var/spool/cron/atjobs/{job}'.format(
        job=jobid
    )
    if __salt__['file.file_exists'](atjob_file):
        with salt.utils.files.fopen(atjob_file, 'r') as rfh:
            return ''.join([salt.utils.stringutils.to_unicode(x)
                            for x in rfh.readlines()])
    else:
        return {'error': 'invalid job id \'{0}\''.format(jobid)}


def _atq(**kwargs):
    '''
    Return match jobs list
    '''

    jobs = []

    runas = kwargs.get('runas', None)
    tag = kwargs.get('tag', None)
    hour = kwargs.get('hour', None)
    minute = kwargs.get('minute', None)
    day = kwargs.get('day', None)
    month = kwargs.get('month', None)
    year = kwargs.get('year', None)
    if year and len(six.text_type(year)) == 2:
        year = '20{0}'.format(year)

    jobinfo = atq()['jobs']
    if not jobinfo:
        return {'jobs': jobs}

    for job in jobinfo:

        if not runas:
            pass
        elif runas == job['user']:
            pass
        else:
            continue

        if not tag:
            pass
        elif tag == job['tag']:
            pass
        else:
            continue

        if not hour:
            pass
        elif '{0:02d}'.format(int(hour)) == job['time'].split(':')[0]:
            pass
        else:
            continue

        if not minute:
            pass
        elif '{0:02d}'.format(int(minute)) == job['time'].split(':')[1]:
            pass
        else:
            continue

        if not day:
            pass
        elif '{0:02d}'.format(int(day)) == job['date'].split('-')[2]:
            pass
        else:
            continue

        if not month:
            pass
        elif '{0:02d}'.format(int(month)) == job['date'].split('-')[1]:
            pass
        else:
            continue

        if not year:
            pass
        elif year == job['date'].split('-')[0]:
            pass
        else:
            continue

        jobs.append(job)

    if not jobs:
        note = 'No match jobs or time format error'
        return {'jobs': jobs, 'note': note}

    return {'jobs': jobs}


def jobcheck(**kwargs):
    '''
    Check the job from queue.
    The kwargs dict include 'hour minute day month year tag runas'
    Other parameters will be ignored.

    CLI Example:

    .. code-block:: bash

        salt '*' at.jobcheck runas=jam day=13
        salt '*' at.jobcheck day=13 month=12 year=13 tag=rose
    '''

    if not kwargs:
        return {'error': 'You have given a condition'}

    return _atq(**kwargs)


# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4