# Buggy code
def get_diff(
    file1,
    file2,
    saltenv="base",
    show_filenames=True,
    show_changes=True,
    template=False,
    source_hash_file1=None,
    source_hash_file2=None,
):
    """
    Return unified diff of two files

    file1
        The first file to feed into the diff utility

        .. versionchanged:: 2018.3.0
            Can now be either a local or remote file. In earlier releases,
            thuis had to be a file local to the minion.

    file2
        The second file to feed into the diff utility

        .. versionchanged:: 2018.3.0
            Can now be either a local or remote file. In earlier releases, this
            had to be a file on the salt fileserver (i.e.
            ``salt://somefile.txt``)

    show_filenames : True
        Set to ``False`` to hide the filenames in the top two lines of the
        diff.

    show_changes : True
        If set to ``False``, and there are differences, then instead of a diff
        a simple message stating that show_changes is set to ``False`` will be
        returned.

    template : False
        Set to ``True`` if two templates are being compared. This is not useful
        except for within states, with the ``obfuscate_templates`` option set
        to ``True``.

        .. versionadded:: 2018.3.0

    source_hash_file1
        If ``file1`` is an http(s)/ftp URL and the file exists in the minion's
        file cache, this option can be passed to keep the minion from
        re-downloading the archive if the cached copy matches the specified
        hash.

        .. versionadded:: 2018.3.0

    source_hash_file2
        If ``file2`` is an http(s)/ftp URL and the file exists in the minion's
        file cache, this option can be passed to keep the minion from
        re-downloading the archive if the cached copy matches the specified
        hash.

        .. versionadded:: 2018.3.0

    CLI Examples:

    .. code-block:: bash

        salt '*' file.get_diff /home/fred/.vimrc salt://users/fred/.vimrc
        salt '*' file.get_diff /tmp/foo.txt /tmp/bar.txt
    """
    files = (file1, file2)
    source_hashes = (source_hash_file1, source_hash_file2)
    paths = []
    errors = []

    for filename, source_hash in zip(files, source_hashes):
        try:
            # Local file paths will just return the same path back when passed
            # to cp.cache_file.
            cached_path = __salt__["cp.cache_file"](
                filename, saltenv, source_hash=source_hash
            )
            if cached_path is False:
                errors.append(
                    "File {0} not found".format(
                        salt.utils.stringutils.to_unicode(filename)
                    )
                )
                continue
            paths.append(cached_path)
        except MinionError as exc:
            errors.append(salt.utils.stringutils.to_unicode(exc.__str__()))
            continue

    if errors:
        raise CommandExecutionError("Failed to cache one or more files", info=errors)

    args = []
    for filename in files:
        try:
            with salt.utils.files.fopen(filename, "rb") as fp_:
                args.append(fp_.readlines())
        except (IOError, OSError) as exc:
            raise CommandExecutionError(
                "Failed to read {0}: {1}".format(
                    salt.utils.stringutils.to_unicode(filename), exc.strerror
                )
            )

    if args[0] != args[1]:
        if template and __salt__["config.option"]("obfuscate_templates"):
            ret = "<Obfuscated Template>"
        elif not show_changes:
            ret = "<show_changes=False>"
        else:
            bdiff = _binary_replace(*files)
            if bdiff:
                ret = bdiff
            else:
                if show_filenames:
                    args.extend(files)
                ret = __utils__["stringutils.get_diff"](*args)
        return ret
    return ""


# Traceback (CommandExecutionError)
[INFO    ] Starting a new job with PID 7373
[DEBUG   ] LazyLoaded cp.cache_file
[DEBUG   ] LazyLoaded direct_call.execute
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506', u'aes')
[DEBUG   ] Initializing new AsyncAuth for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506')
[DEBUG   ] Connecting the Minion to the Master URI (for the return server): tcp://10.1.1.100:4506
[DEBUG   ] Trying to connect to: tcp://10.1.1.100:4506
[DEBUG   ] In saltenv 'base', looking at rel_path 'aio/archive/archive.tar.md5' to resolve 'salt://aio/archive/archive.tar.md5'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/aio/archive/archive.tar.md5' to resolve 'salt://aio/archive/archive.tar.md5'
[DEBUG   ] Minion return retry timer set to 7 seconds (randomized)
[INFO    ] Returning information for job: 20180808190340722939
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506', u'aes')
[DEBUG   ] Initializing new AsyncAuth for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506')
[DEBUG   ] Connecting the Minion to the Master URI (for the return server): tcp://10.1.1.100:4506
[DEBUG   ] Trying to connect to: tcp://10.1.1.100:4506
[DEBUG   ] minion return: {u'fun_args': [u'salt://aio/archive/archive.tar.md5'], u'jid': u'20180808190340722939', u'return': u'/var/cache/salt/minion/files/base/aio/archive/archive.tar.md5', u'retcode': 0, u'success': True, u'fun': u'cp.cache_file'}
[INFO    ] User root Executing command file.get_diff with jid 20180808190346684060
[DEBUG   ] Command details {u'tgt_type': u'glob', u'jid': u'20180808190346684060', u'tgt': u'minion-host', u'ret': u'', u'user': u'root', u'arg': [u'/.archive.tar.md5', u'salt://aio/archive/archive.tar.md5'], u'fun': u'file.get_diff'}
[INFO    ] Starting a new job with PID 7387
[DEBUG   ] LazyLoaded file.get_diff
[DEBUG   ] LazyLoaded direct_call.execute
[DEBUG   ] LazyLoaded cp.cache_file
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506', u'aes')
[DEBUG   ] Initializing new AsyncAuth for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506')
[DEBUG   ] Connecting the Minion to the Master URI (for the return server): tcp://10.1.1.100:4506
[DEBUG   ] Trying to connect to: tcp://10.1.1.100:4506
[DEBUG   ] In saltenv 'base', looking at rel_path 'aio/archive/archive.tar.md5' to resolve 'salt://aio/archive/archive.tar.md5'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/aio/archive/archive.tar.md5' to resolve 'salt://aio/archive/archive.tar.md5'
[ERROR   ] A command in 'file.get_diff' had a problem: Failed to read salt://aio/archive/archive.tar.md5: No such file or directory
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/salt/minion.py", line 1606, in _thread_return
return_data = minion_instance.executors[fname](opts, data, func, args, kwargs)
File "/usr/lib/python2.7/site-packages/salt/executors/direct_call.py", line 12, in execute
return func(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/salt/modules/file.py", line 4990, in get_diff
exc.strerror
CommandExecutionError: Failed to read salt://aio/archive/archive.tar.md5: No such file or directory
[DEBUG   ] Minion return retry timer set to 8 seconds (randomized)
[INFO    ] Returning information for job: 20180808190346684060
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506', u'aes')
[DEBUG   ] Initializing new AsyncAuth for (u'/etc/salt/pki/minion', u'minion-host', u'tcp://10.1.1.100:4506')
[DEBUG   ] Connecting the Minion to the Master URI (for the return server): tcp://10.1.1.100:4506
[DEBUG   ] Trying to connect to: tcp://10.1.1.100:4506
[DEBUG   ] minion return: {u'fun_args': [u'/.archive.tar.md5', u'salt://aio/archive/archive.tar.md5'], u'jid': u'20180808190346684060', u'return': u'ERROR: Failed to read salt://aio/archive/archive.tar.md5: No such file or directory', u'success': False, u'fun': u'file.get_diff', u'out': u'nested'}

# Fixed code
def get_diff(
    file1,
    file2,
    saltenv="base",
    show_filenames=True,
    show_changes=True,
    template=False,
    source_hash_file1=None,
    source_hash_file2=None,
):
    """
    Return unified diff of two files

    file1
        The first file to feed into the diff utility

        .. versionchanged:: 2018.3.0
            Can now be either a local or remote file. In earlier releases,
            thuis had to be a file local to the minion.

    file2
        The second file to feed into the diff utility

        .. versionchanged:: 2018.3.0
            Can now be either a local or remote file. In earlier releases, this
            had to be a file on the salt fileserver (i.e.
            ``salt://somefile.txt``)

    show_filenames : True
        Set to ``False`` to hide the filenames in the top two lines of the
        diff.

    show_changes : True
        If set to ``False``, and there are differences, then instead of a diff
        a simple message stating that show_changes is set to ``False`` will be
        returned.

    template : False
        Set to ``True`` if two templates are being compared. This is not useful
        except for within states, with the ``obfuscate_templates`` option set
        to ``True``.

        .. versionadded:: 2018.3.0

    source_hash_file1
        If ``file1`` is an http(s)/ftp URL and the file exists in the minion's
        file cache, this option can be passed to keep the minion from
        re-downloading the archive if the cached copy matches the specified
        hash.

        .. versionadded:: 2018.3.0

    source_hash_file2
        If ``file2`` is an http(s)/ftp URL and the file exists in the minion's
        file cache, this option can be passed to keep the minion from
        re-downloading the archive if the cached copy matches the specified
        hash.

        .. versionadded:: 2018.3.0

    CLI Examples:

    .. code-block:: bash

        salt '*' file.get_diff /home/fred/.vimrc salt://users/fred/.vimrc
        salt '*' file.get_diff /tmp/foo.txt /tmp/bar.txt
    """
    files = (file1, file2)
    source_hashes = (source_hash_file1, source_hash_file2)
    paths = []
    errors = []

    for filename, source_hash in zip(files, source_hashes):
        try:
            # Local file paths will just return the same path back when passed
            # to cp.cache_file.
            cached_path = __salt__["cp.cache_file"](
                filename, saltenv, source_hash=source_hash
            )
            if cached_path is False:
                errors.append(
                    "File {0} not found".format(
                        salt.utils.stringutils.to_unicode(filename)
                    )
                )
                continue
            paths.append(cached_path)
        except MinionError as exc:
            errors.append(salt.utils.stringutils.to_unicode(exc.__str__()))
            continue

    if errors:
        raise CommandExecutionError("Failed to cache one or more files", info=errors)

    args = []
    for filename in paths:
        try:
            with salt.utils.files.fopen(filename, "rb") as fp_:
                args.append(fp_.readlines())
        except (IOError, OSError) as exc:
            raise CommandExecutionError(
                "Failed to read {0}: {1}".format(
                    salt.utils.stringutils.to_unicode(filename), exc.strerror
                )
            )

    if args[0] != args[1]:
        if template and __salt__["config.option"]("obfuscate_templates"):
            ret = "<Obfuscated Template>"
        elif not show_changes:
            ret = "<show_changes=False>"
        else:
            bdiff = _binary_replace(*paths)  # pylint: disable=no-value-for-parameter
            if bdiff:
                ret = bdiff
            else:
                if show_filenames:
                    args.extend(paths)
                ret = __utils__["stringutils.get_diff"](*args)
        return ret
    return ""