# Buggy code
def append(
    name,
    text=None,
    makedirs=False,
    source=None,
    source_hash=None,
    template="jinja",
    sources=None,
    source_hashes=None,
    defaults=None,
    context=None,
):
    """
    Ensure that some text appears at the end of a file.

    The text will not be appended if it already exists in the file.
    A single string of text or a list of strings may be appended.

    name
        The location of the file to append to.

    text
        The text to be appended, which can be a single string or a list
        of strings.

    makedirs
        If the file is located in a path without a parent directory,
        then the state will fail. If makedirs is set to True, then
        the parent directories will be created to facilitate the
        creation of the named file. Defaults to False.

    source
        A single source file to append. This source file can be hosted on either
        the salt master server, or on an HTTP or FTP server. Both HTTPS and
        HTTP are supported as well as downloading directly from Amazon S3
        compatible URLs with both pre-configured and automatic IAM credentials
        (see s3.get state documentation). File retrieval from Openstack Swift
        object storage is supported via swift://container/object_path URLs
        (see swift.get documentation).

        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 an HTTP or FTP server, the source_hash argument
        is also required.

    source_hash
        This can be one of the following:
            1. a source hash string
            2. the URI of a file that contains source hash strings

        The function accepts the first encountered long unbroken alphanumeric
        string of correct length as a valid hash, in order from most secure to
        least secure:

        .. code-block:: text

            Type    Length
            ======  ======
            sha512     128
            sha384      96
            sha256      64
            sha224      56
            sha1        40
            md5         32

        The file can contain several checksums for several files. Each line
        must contain both the file name and the hash.  If no file name is
        matched, the first hash encountered will be used, otherwise the most
        secure hash with the correct source file name will be used.

        Debian file type ``*.dsc`` is supported.

        Examples:

        .. code-block:: text

            /etc/rc.conf ef6e82e4006dee563d98ada2a2a80a27
            sha254c8525aee419eb649f0233be91c151178b30f0dff8ebbdcc8de71b1d5c8bcc06a  /etc/resolv.conf
            ead48423703509d37c4a90e6a0d53e143b6fc268

        Known issues:
            If the remote server URL has the hash file as an apparent
            sub-directory of the source file, the module will discover that it
            has already cached a directory where a file should be cached. For
            example:

            .. code-block:: yaml

                tomdroid-src-0.7.3.tar.gz:
                  file.managed:
                    - name: /tmp/tomdroid-src-0.7.3.tar.gz
                    - source: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz
                    - source_hash: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz/+md5

    template : ``jinja``
        The named templating engine will be used to render the appended-to
        file. Defaults to jinja.

    sources
        A list of source files to append. If the files are hosted on an HTTP or
        FTP server, the source_hashes argument is also required.

    source_hashes
        A list of source_hashes corresponding to the sources list specified in
        the sources argument.

    defaults
        Default context passed to the template.

    context
        Overrides default context variables passed to the template.

    Multi-line example:

    .. code-block:: yaml

        /etc/motd:
          file.append:
            - text: |
                Thou hadst better eat salt with the Philosophers of Greece,
                than sugar with the Courtiers of Italy.
                - Benjamin Franklin

    Multiple lines of text:

    .. code-block:: yaml

        /etc/motd:
          file.append:
            - text:
              - Trust no one unless you have eaten much salt with him.
              - "Salt is born of the purest of parents: the sun and the sea."

    Gather text from multiple template files:

    .. code-block:: yaml

        /etc/motd:
          file:
              - append
              - template: jinja
              - sources:
                  - salt://motd/devops-messages.tmpl
                  - salt://motd/hr-messages.tmpl
                  - salt://motd/general-messages.tmpl

    .. versionadded:: 0.9.5
    """
    ret = {"name": name, "changes": {}, "result": False, "comment": ""}

    if sources is None:
        sources = []

    if source_hashes is None:
        source_hashes = []

    # Add sources and source_hashes with template support
    # NOTE: FIX 'text' and any 'source' are mutually exclusive as 'text'
    #       is re-assigned in the original code.
    (ok_, err, sl_) = _unify_sources_and_hashes(
        source=source,
        source_hash=source_hash,
        sources=sources,
        source_hashes=source_hashes,
    )
    if not ok_:
        return _error(ret, err)

    if makedirs is True:
        dirname = os.path.dirname(name)
        if not __salt__["file.directory_exists"](dirname):
            __salt__["file.makedirs"](name)
            check_res, check_msg = _check_directory(
                dirname, None, None, False, None, False, False, None
            )
            if not check_res:
                return _error(ret, check_msg)

        # Make sure that we have a file
        __salt__["file.touch"](name)

    check_res, check_msg = _check_file(name)
    if not check_res:
        touch(name, makedirs=makedirs)
        retry_res, retry_msg = _check_file(name)
        if not retry_res:
            return _error(ret, check_msg)

    # Follow the original logic and re-assign 'text' if using source(s)...
    if sl_:
        tmpret = _get_template_texts(
            source_list=sl_, template=template, defaults=defaults, context=context
        )
        if not tmpret["result"]:
            return tmpret
        text = tmpret["data"]

    for index, item in enumerate(text):
        if isinstance(item, integer_types):
            text[index] = str(item)

    if isinstance(text, string_types):
        text = (text,)

    with salt.utils.fopen(name, "rb") as fp_:
        slines = fp_.readlines()

    count = 0
    test_lines = []

    try:
        for chunk in text:
            if __salt__["file.contains_regex_multiline"](
                name, salt.utils.build_whitespace_split_regex(chunk)
            ):
                continue

            try:
                lines = chunk.splitlines()
            except AttributeError:
                log.debug(
                    "Error appending text to {0}; given object is: {1}".format(
                        name, type(chunk)
                    )
                )
                return _error(ret, "Given text is not a string")

            for line in lines:
                if __opts__["test"]:
                    ret["comment"] = "File {0} is set to be updated".format(name)
                    ret["result"] = None
                    test_lines.append("{0}\n".format(line))
                else:
                    __salt__["file.append"](name, line)
                count += 1
    except TypeError:
        ret["comment"] = "No text found to append. Nothing appended"
        ret["result"] = False
        return ret

    if __opts__["test"]:
        nlines = slines + test_lines
        ret["result"] = None
        if slines != nlines:
            if not salt.utils.istextfile(name):
                ret["changes"]["diff"] = "Replace binary file"
            else:
                # Changes happened, add them
                ret["changes"]["diff"] = "".join(difflib.unified_diff(slines, nlines))
        else:
            ret["comment"] = "File {0} is in correct state".format(name)
            ret["result"] = True
        return ret

    with salt.utils.fopen(name, "rb") as fp_:
        nlines = fp_.readlines()

    if slines != nlines:
        if not salt.utils.istextfile(name):
            ret["changes"]["diff"] = "Replace binary file"
        else:
            # Changes happened, add them
            ret["changes"]["diff"] = "".join(difflib.unified_diff(slines, nlines))

    if count:
        ret["comment"] = "Appended {0} lines".format(count)
    else:
        ret["comment"] = "File {0} is in correct state".format(name)
    ret["result"] = True
    return ret


# Traceback (TypeError)
2014-11-24 16:03:42,245 [salt.minion      ][WARNING ] TypeError encountered executing state.highstate: expected string or buffer. See debug log for more info.  Possibly a missing arguments issue:  ArgSpec(args=['test', 'queue'], varargs=None, keywords='kwargs', defaults=(None, False))
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.7/salt/minion.py", line 809, in _thread_return
return_data = func(*args, **kwargs)
File "/usr/lib/pymodules/python2.7/salt/modules/state.py", line 275, in highstate
force=kwargs.get('force', False)
File "/usr/lib/pymodules/python2.7/salt/state.py", line 2546, in call_highstate
return self.state.call_high(high)
File "/usr/lib/pymodules/python2.7/salt/state.py", line 1715, in call_high
ret = self.call_chunks(chunks)
File "/usr/lib/pymodules/python2.7/salt/state.py", line 1437, in call_chunks
running = self.call_chunk(low, running, chunks)
File "/usr/lib/pymodules/python2.7/salt/state.py", line 1562, in call_chunk
status = self.check_requisite(low, running, chunks, True)
File "/usr/lib/pymodules/python2.7/salt/state.py", line 1489, in check_requisite
if (fnmatch.fnmatch(chunk['name'], req_val) or
File "/usr/lib/python2.7/fnmatch.py", line 43, in fnmatch
return fnmatchcase(name, pat)
File "/usr/lib/python2.7/fnmatch.py", line 79, in fnmatchcase
return _cache[pat].match(name) is not None
TypeError: expected string or buffer

# Fixed code
def append(
    name,
    text=None,
    makedirs=False,
    source=None,
    source_hash=None,
    template="jinja",
    sources=None,
    source_hashes=None,
    defaults=None,
    context=None,
):
    """
    Ensure that some text appears at the end of a file.

    The text will not be appended if it already exists in the file.
    A single string of text or a list of strings may be appended.

    name
        The location of the file to append to.

    text
        The text to be appended, which can be a single string or a list
        of strings.

    makedirs
        If the file is located in a path without a parent directory,
        then the state will fail. If makedirs is set to True, then
        the parent directories will be created to facilitate the
        creation of the named file. Defaults to False.

    source
        A single source file to append. This source file can be hosted on either
        the salt master server, or on an HTTP or FTP server. Both HTTPS and
        HTTP are supported as well as downloading directly from Amazon S3
        compatible URLs with both pre-configured and automatic IAM credentials
        (see s3.get state documentation). File retrieval from Openstack Swift
        object storage is supported via swift://container/object_path URLs
        (see swift.get documentation).

        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 an HTTP or FTP server, the source_hash argument
        is also required.

    source_hash
        This can be one of the following:
            1. a source hash string
            2. the URI of a file that contains source hash strings

        The function accepts the first encountered long unbroken alphanumeric
        string of correct length as a valid hash, in order from most secure to
        least secure:

        .. code-block:: text

            Type    Length
            ======  ======
            sha512     128
            sha384      96
            sha256      64
            sha224      56
            sha1        40
            md5         32

        The file can contain several checksums for several files. Each line
        must contain both the file name and the hash.  If no file name is
        matched, the first hash encountered will be used, otherwise the most
        secure hash with the correct source file name will be used.

        Debian file type ``*.dsc`` is supported.

        Examples:

        .. code-block:: text

            /etc/rc.conf ef6e82e4006dee563d98ada2a2a80a27
            sha254c8525aee419eb649f0233be91c151178b30f0dff8ebbdcc8de71b1d5c8bcc06a  /etc/resolv.conf
            ead48423703509d37c4a90e6a0d53e143b6fc268

        Known issues:
            If the remote server URL has the hash file as an apparent
            sub-directory of the source file, the module will discover that it
            has already cached a directory where a file should be cached. For
            example:

            .. code-block:: yaml

                tomdroid-src-0.7.3.tar.gz:
                  file.managed:
                    - name: /tmp/tomdroid-src-0.7.3.tar.gz
                    - source: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz
                    - source_hash: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz/+md5

    template : ``jinja``
        The named templating engine will be used to render the appended-to
        file. Defaults to jinja.

    sources
        A list of source files to append. If the files are hosted on an HTTP or
        FTP server, the source_hashes argument is also required.

    source_hashes
        A list of source_hashes corresponding to the sources list specified in
        the sources argument.

    defaults
        Default context passed to the template.

    context
        Overrides default context variables passed to the template.

    Multi-line example:

    .. code-block:: yaml

        /etc/motd:
          file.append:
            - text: |
                Thou hadst better eat salt with the Philosophers of Greece,
                than sugar with the Courtiers of Italy.
                - Benjamin Franklin

    Multiple lines of text:

    .. code-block:: yaml

        /etc/motd:
          file.append:
            - text:
              - Trust no one unless you have eaten much salt with him.
              - "Salt is born of the purest of parents: the sun and the sea."

    Gather text from multiple template files:

    .. code-block:: yaml

        /etc/motd:
          file:
              - append
              - template: jinja
              - sources:
                  - salt://motd/devops-messages.tmpl
                  - salt://motd/hr-messages.tmpl
                  - salt://motd/general-messages.tmpl

    .. versionadded:: 0.9.5
    """
    ret = {"name": name, "changes": {}, "result": False, "comment": ""}
    if not name:
        return _error(ret, "Must provide name to file.append")

    if sources is None:
        sources = []

    if source_hashes is None:
        source_hashes = []

    # Add sources and source_hashes with template support
    # NOTE: FIX 'text' and any 'source' are mutually exclusive as 'text'
    #       is re-assigned in the original code.
    (ok_, err, sl_) = _unify_sources_and_hashes(
        source=source,
        source_hash=source_hash,
        sources=sources,
        source_hashes=source_hashes,
    )
    if not ok_:
        return _error(ret, err)

    if makedirs is True:
        dirname = os.path.dirname(name)
        if not __salt__["file.directory_exists"](dirname):
            __salt__["file.makedirs"](name)
            check_res, check_msg = _check_directory(
                dirname, None, None, False, None, False, False, None
            )
            if not check_res:
                return _error(ret, check_msg)

        # Make sure that we have a file
        __salt__["file.touch"](name)

    check_res, check_msg = _check_file(name)
    if not check_res:
        touch(name, makedirs=makedirs)
        retry_res, retry_msg = _check_file(name)
        if not retry_res:
            return _error(ret, check_msg)

    # Follow the original logic and re-assign 'text' if using source(s)...
    if sl_:
        tmpret = _get_template_texts(
            source_list=sl_, template=template, defaults=defaults, context=context
        )
        if not tmpret["result"]:
            return tmpret
        text = tmpret["data"]

    for index, item in enumerate(text):
        if isinstance(item, integer_types):
            text[index] = str(item)

    if isinstance(text, string_types):
        text = (text,)

    with salt.utils.fopen(name, "rb") as fp_:
        slines = fp_.readlines()

    count = 0
    test_lines = []

    try:
        for chunk in text:
            if __salt__["file.contains_regex_multiline"](
                name, salt.utils.build_whitespace_split_regex(chunk)
            ):
                continue

            try:
                lines = chunk.splitlines()
            except AttributeError:
                log.debug(
                    "Error appending text to {0}; given object is: {1}".format(
                        name, type(chunk)
                    )
                )
                return _error(ret, "Given text is not a string")

            for line in lines:
                if __opts__["test"]:
                    ret["comment"] = "File {0} is set to be updated".format(name)
                    ret["result"] = None
                    test_lines.append("{0}\n".format(line))
                else:
                    __salt__["file.append"](name, line)
                count += 1
    except TypeError:
        ret["comment"] = "No text found to append. Nothing appended"
        ret["result"] = False
        return ret

    if __opts__["test"]:
        nlines = slines + test_lines
        ret["result"] = None
        if slines != nlines:
            if not salt.utils.istextfile(name):
                ret["changes"]["diff"] = "Replace binary file"
            else:
                # Changes happened, add them
                ret["changes"]["diff"] = "".join(difflib.unified_diff(slines, nlines))
        else:
            ret["comment"] = "File {0} is in correct state".format(name)
            ret["result"] = True
        return ret

    with salt.utils.fopen(name, "rb") as fp_:
        nlines = fp_.readlines()

    if slines != nlines:
        if not salt.utils.istextfile(name):
            ret["changes"]["diff"] = "Replace binary file"
        else:
            # Changes happened, add them
            ret["changes"]["diff"] = "".join(difflib.unified_diff(slines, nlines))

    if count:
        ret["comment"] = "Appended {0} lines".format(count)
    else:
        ret["comment"] = "File {0} is in correct state".format(name)
    ret["result"] = True
    return ret