Provides file.managed-like functionality (templating, etc.) for a pre-made
    crontab file, to be assigned to a given user.

    name
        The source file to be used as the crontab. This source file can be
        hosted on either the salt master server, or on an HTTP or FTP server.
        For files hosted on the salt file server, if the file is located on
        the master in the directory named spam, and is called eggs, the source
        string is ``salt://spam/eggs``

        If the file is hosted on a HTTP or FTP server then the source_hash
        argument is also required

    source_hash
        This can be either a file which contains a source hash string for
        the source, or a source hash string. The source hash string is the
        hash algorithm followed by the hash of the file:
        ``md5=e138491e9d5b97023cea823fe17bac22``

    source_hash_name
        When ``source_hash`` refers to a hash file, Salt will try to find the
        correct hash by matching the filename/URI associated with that hash. By
        default, Salt will look for the filename being managed. When managing a
        file at path ``/tmp/foo.txt``, then the following line in a hash file
        would match:

        .. code-block:: text

            acbd18db4cc2f85cedef654fccc4a4d8    foo.txt

        However, sometimes a hash file will include multiple similar paths:

        .. code-block:: text

            37b51d194a7513e45b56f6524f2d51f2    ./dir1/foo.txt
            acbd18db4cc2f85cedef654fccc4a4d8    ./dir2/foo.txt
            73feffa4b7f6bb68e44cf984c85f6e88    ./dir3/foo.txt

        In cases like this, Salt may match the incorrect hash. This argument
        can be used to tell Salt which filename to match, to ensure that the
        correct hash is identified. For example:

        .. code-block:: yaml

            foo_crontab:
              cron.file:
                - name: https://mydomain.tld/dir2/foo.txt
                - source_hash: https://mydomain.tld/hashes
                - source_hash_name: ./dir2/foo.txt

        .. note::
            This argument must contain the full filename entry from the
            checksum file, as this argument is meant to disambiguate matches
            for multiple files that have the same basename. So, in the
            example above, simply using ``foo.txt`` would not match.

        .. versionadded:: 2016.3.5

    user
        The user to whom the crontab should be assigned. This defaults to
        root.

    template
        If this setting is applied then the named templating engine will be
        used to render the downloaded file. Currently, jinja and mako are
        supported.

    context
        Overrides default context variables passed to the template.

    replace
        If the crontab should be replaced, if False then this command will
        be ignored if a crontab exists for the specified user. Default is True.

    defaults
        Default context passed to the template.

    backup
        Overrides the default backup mode for the user's crontab.

def file(name,
         source_hash='',
         source_hash_name=None,
         user='root',
         template=None,
         context=None,
         replace=True,
         defaults=None,
         backup='',
         **kwargs):
    '''
    Provides file.managed-like functionality (templating, etc.) for a pre-made
    crontab file, to be assigned to a given user.

    name
        The source file to be used as the crontab. This source file can be
        hosted on either the salt master server, or on an HTTP or FTP server.
        For files hosted on the salt file server, if the file is located on
        the master in the directory named spam, and is called eggs, the source
        string is ``salt://spam/eggs``

        If the file is hosted on a HTTP or FTP server then the source_hash
        argument is also required

    source_hash
        This can be either a file which contains a source hash string for
        the source, or a source hash string. The source hash string is the
        hash algorithm followed by the hash of the file:
        ``md5=e138491e9d5b97023cea823fe17bac22``

    source_hash_name
        When ``source_hash`` refers to a hash file, Salt will try to find the
        correct hash by matching the filename/URI associated with that hash. By
        default, Salt will look for the filename being managed. When managing a
        file at path ``/tmp/foo.txt``, then the following line in a hash file
        would match:

        .. code-block:: text

            acbd18db4cc2f85cedef654fccc4a4d8    foo.txt

        However, sometimes a hash file will include multiple similar paths:

        .. code-block:: text

            37b51d194a7513e45b56f6524f2d51f2    ./dir1/foo.txt
            acbd18db4cc2f85cedef654fccc4a4d8    ./dir2/foo.txt
            73feffa4b7f6bb68e44cf984c85f6e88    ./dir3/foo.txt

        In cases like this, Salt may match the incorrect hash. This argument
        can be used to tell Salt which filename to match, to ensure that the
        correct hash is identified. For example:

        .. code-block:: yaml

            foo_crontab:
              cron.file:
                - name: https://mydomain.tld/dir2/foo.txt
                - source_hash: https://mydomain.tld/hashes
                - source_hash_name: ./dir2/foo.txt

        .. note::
            This argument must contain the full filename entry from the
            checksum file, as this argument is meant to disambiguate matches
            for multiple files that have the same basename. So, in the
            example above, simply using ``foo.txt`` would not match.

        .. versionadded:: 2016.3.5

    user
        The user to whom the crontab should be assigned. This defaults to
        root.

    template
        If this setting is applied then the named templating engine will be
        used to render the downloaded file. Currently, jinja and mako are
        supported.

    context
        Overrides default context variables passed to the template.

    replace
        If the crontab should be replaced, if False then this command will
        be ignored if a crontab exists for the specified user. Default is True.

    defaults
        Default context passed to the template.

    backup
        Overrides the default backup mode for the user's crontab.
    '''
    # Initial set up
    mode = '0600'

    try:
        group = __salt__['user.info'](user)['groups'][0]
    except Exception:
        ret = {'changes': {},
               'comment': "Could not identify group for user {0}".format(user),
               'name': name,
               'result': False}
        return ret

    cron_path = salt.utils.files.mkstemp()
    with salt.utils.files.fopen(cron_path, 'w+') as fp_:
        raw_cron = __salt__['cron.raw_cron'](user)
        if not raw_cron.endswith('\n'):
            raw_cron = "{0}\n".format(raw_cron)
        fp_.write(salt.utils.stringutils.to_str(raw_cron))

    ret = {'changes': {},
           'comment': '',
           'name': name,
           'result': True}

    # Avoid variable naming confusion in below module calls, since ID
    # declaration for this state will be a source URI.
    source = name

    if not replace and os.stat(cron_path).st_size > 0:
        ret['comment'] = 'User {0} already has a crontab. No changes ' \
                         'made'.format(user)
        os.unlink(cron_path)
        return ret

    if __opts__['test']:
        fcm = __salt__['file.check_managed'](name=cron_path,
                                             source=source,
                                             source_hash=source_hash,
                                             source_hash_name=source_hash_name,
                                             user=user,
                                             group=group,
                                             mode=mode,
                                             attrs=[],  # no special attrs for cron
                                             template=template,
                                             context=context,
                                             defaults=defaults,
                                             saltenv=__env__,
                                             **kwargs
                                             )
        ret['result'], ret['comment'] = fcm
        os.unlink(cron_path)
        return ret

    # If the source is a list then find which file exists
    source, source_hash = __salt__['file.source_list'](source,
                                                       source_hash,
                                                       __env__)

    # Gather the source file from the server
    try:
        sfn, source_sum, comment = __salt__['file.get_managed'](
            name=cron_path,
            template=template,
            source=source,
            source_hash=source_hash,
            source_hash_name=source_hash_name,
            user=user,
            group=group,
            mode=mode,
            attrs=[],
            saltenv=__env__,
            context=context,
            defaults=defaults,
            skip_verify=False,        # skip_verify
            **kwargs
        )
    except Exception as exc:
        ret['result'] = False
        ret['changes'] = {}
        ret['comment'] = 'Unable to manage file: {0}'.format(exc)
        return ret

    if comment:
        ret['comment'] = comment
        ret['result'] = False
        os.unlink(cron_path)
        return ret

    try:
        ret = __salt__['file.manage_file'](
            name=cron_path,
            sfn=sfn,
            ret=ret,
            source=source,
            source_sum=source_sum,
            user=user,
            group=group,
            mode=mode,
            attrs=[],
            saltenv=__env__,
            backup=backup
        )
    except Exception as exc:
        ret['result'] = False
        ret['changes'] = {}
        ret['comment'] = 'Unable to manage file: {0}'.format(exc)
        return ret

    cron_ret = None
    if "diff" in ret['changes']:
        cron_ret = __salt__['cron.write_cron_file_verbose'](user, cron_path)
        # Check cmd return code and show success or failure
        if cron_ret['retcode'] == 0:
            ret['comment'] = 'Crontab for user {0} was updated'.format(user)
            ret['result'] = True
            ret['changes'] = ret['changes']
        else:
            ret['comment'] = 'Unable to update user {0} crontab {1}.' \
                             ' Error: {2}'.format(user, cron_path, cron_ret['stderr'])
            ret['result'] = False
            ret['changes'] = {}
    elif ret['result']:
        ret['comment'] = 'Crontab for user {0} is in the correct ' \
                         'state'.format(user)
        ret['changes'] = {}

    os.unlink(cron_path)
    return ret