# 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 idx, filename in enumerate(files):
        try:
            with salt.utils.files.fopen(filename, "r") as fp_:
                args.append(
                    [salt.utils.stringutils.to_unicode(x) for x in 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([salt.utils.stringutils.to_unicode(x) for x in files])
                ret = salt.utils.locales.sdecode(
                    "".join(difflib.unified_diff(*args))  # pylint: disable=no-value-for-parameter
                )
        return ret

    return ""


# Traceback (UnicodeDecodeError)
[DEBUG   ] Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/salt/states/file.py", line 2569, in managed
**kwargs)
File "/usr/lib/python2.7/dist-packages/salt/modules/file.py", line 5158, in manage_file
real_name, sfn, show_filenames=False)
File "/usr/lib/python2.7/dist-packages/salt/modules/file.py", line 4921, in get_diff
for x in fp_.readlines()])
File "/usr/lib/python2.7/dist-packages/salt/utils/stringutils.py", line 114, in to_unicode
return _normalize(s.decode('utf-8', errors))
File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x99 in position 25: invalid start byte

[ERROR   ] Unable to manage file: 'utf8' codec can't decode byte 0x99 in position 25: invalid start byte
[INFO    ] Completed state [/opt/cni/bin/calico] at time 09:48:20.230734 (duration_in_ms=1670.37)

# 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 idx, filename in enumerate(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 = "".join(difflib.unified_diff(*salt.utils.data.decode(args)))
    return ret