# Buggy code
def run_command(
    self,
    args,
    check_rc=False,
    close_fds=True,
    executable=None,
    data=None,
    binary_data=False,
    path_prefix=None,
    cwd=None,
    use_unsafe_shell=False,
    prompt_regex=None,
    environ_update=None,
):
    """
    Execute a command, returns rc, stdout, and stderr.

    :arg args: is the command to run
        * If args is a list, the command will be run with shell=False.
        * If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False
        * If args is a string and use_unsafe_shell=True it runs with shell=True.
    :kw check_rc: Whether to call fail_json in case of non zero RC.
        Default False
    :kw close_fds: See documentation for subprocess.Popen(). Default True
    :kw executable: See documentation for subprocess.Popen(). Default None
    :kw data: If given, information to write to the stdin of the command
    :kw binary_data: If False, append a newline to the data.  Default False
    :kw path_prefix: If given, additional path to find the command in.
        This adds to the PATH environment vairable so helper commands in
        the same directory can also be found
    :kw cwd: iIf given, working directory to run the command inside
    :kw use_unsafe_shell: See `args` parameter.  Default False
    :kw prompt_regex: Regex string (not a compiled regex) which can be
        used to detect prompts in the stdout which would otherwise cause
        the execution to hang (especially if no input data is specified)
    :kwarg environ_update: dictionary to *update* os.environ with
    """

    shell = False
    if isinstance(args, list):
        if use_unsafe_shell:
            args = " ".join([pipes.quote(x) for x in args])
            shell = True
    elif isinstance(args, basestring) and use_unsafe_shell:
        shell = True
    elif isinstance(args, basestring):
        if isinstance(args, unicode):
            args = args.encode("utf-8")
        args = shlex.split(args)
    else:
        msg = "Argument 'args' to run_command must be list or string"
        self.fail_json(rc=257, cmd=args, msg=msg)

    prompt_re = None
    if prompt_regex:
        try:
            prompt_re = re.compile(prompt_regex, re.MULTILINE)
        except re.error:
            self.fail_json(msg="invalid prompt regular expression given to run_command")

    # expand things like $HOME and ~
    if not shell:
        args = [
            os.path.expandvars(os.path.expanduser(x)) for x in args if x is not None
        ]

    rc = 0
    msg = None
    st_in = None

    # Manipulate the environ we'll send to the new process
    old_env_vals = {}
    # We can set this from both an attribute and per call
    for key, val in self.run_command_environ_update.items():
        old_env_vals[key] = os.environ.get(key, None)
        os.environ[key] = val
    if environ_update:
        for key, val in environ_update.items():
            old_env_vals[key] = os.environ.get(key, None)
            os.environ[key] = val
    if path_prefix:
        old_env_vals["PATH"] = os.environ["PATH"]
        os.environ["PATH"] = "%s:%s" % (path_prefix, os.environ["PATH"])

    # create a printable version of the command for use
    # in reporting later, which strips out things like
    # passwords from the args list
    if isinstance(args, basestring):
        if isinstance(args, unicode):
            b_args = args.encode("utf-8")
        else:
            b_args = args
        to_clean_args = shlex.split(b_args)
        del b_args
    else:
        to_clean_args = args

    clean_args = []
    is_passwd = False
    for arg in to_clean_args:
        if is_passwd:
            is_passwd = False
            clean_args.append("********")
            continue
        if PASSWD_ARG_RE.match(arg):
            sep_idx = arg.find("=")
            if sep_idx > -1:
                clean_args.append("%s=********" % arg[:sep_idx])
                continue
            else:
                is_passwd = True
        arg = heuristic_log_sanitize(arg, self.no_log_values)
        clean_args.append(arg)
    clean_args = " ".join(pipes.quote(arg) for arg in clean_args)

    if data:
        st_in = subprocess.PIPE

    kwargs = dict(
        executable=executable,
        shell=shell,
        close_fds=close_fds,
        stdin=st_in,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=os.environ,
    )

    if cwd and os.path.isdir(cwd):
        kwargs["cwd"] = cwd

    # store the pwd
    prev_dir = os.getcwd()

    # make sure we're in the right working directory
    if cwd and os.path.isdir(cwd):
        try:
            os.chdir(cwd)
        except (OSError, IOError):
            e = get_exception()
            self.fail_json(rc=e.errno, msg="Could not open %s, %s" % (cwd, str(e)))

    try:
        if self._debug:
            if isinstance(args, list):
                running = " ".join(args)
            else:
                running = args
            self.log("Executing: " + running)
        cmd = subprocess.Popen(args, **kwargs)

        # the communication logic here is essentially taken from that
        # of the _communicate() function in ssh.py

        stdout = ""
        stderr = ""
        rpipes = [cmd.stdout, cmd.stderr]

        if data:
            if not binary_data:
                data += "\n"
            cmd.stdin.write(data)
            cmd.stdin.close()

        while True:
            rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
            if cmd.stdout in rfd:
                dat = os.read(cmd.stdout.fileno(), 9000)
                stdout += dat
                if dat == "":
                    rpipes.remove(cmd.stdout)
            if cmd.stderr in rfd:
                dat = os.read(cmd.stderr.fileno(), 9000)
                stderr += dat
                if dat == "":
                    rpipes.remove(cmd.stderr)
            # if we're checking for prompts, do it now
            if prompt_re:
                if prompt_re.search(stdout) and not data:
                    return (
                        257,
                        stdout,
                        "A prompt was encountered while running a command, but no input data was specified",
                    )
            # only break out if no pipes are left to read or
            # the pipes are completely read and
            # the process is terminated
            if (not rpipes or not rfd) and cmd.poll() is not None:
                break
            # No pipes are left to read but process is not yet terminated
            # Only then it is safe to wait for the process to be finished
            # NOTE: Actually cmd.poll() is always None here if rpipes is empty
            elif not rpipes and cmd.poll() == None:
                cmd.wait()
                # The process is terminated. Since no pipes to read from are
                # left, there is no need to call select() again.
                break

        cmd.stdout.close()
        cmd.stderr.close()

        rc = cmd.returncode
    except (OSError, IOError):
        e = get_exception()
        self.fail_json(rc=e.errno, msg=str(e), cmd=clean_args)
    except:
        self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)

    # Restore env settings
    for key, val in old_env_vals.items():
        if val is None:
            del os.environ[key]
        else:
            os.environ[key] = val

    if rc != 0 and check_rc:
        msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
        self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)

    # reset the pwd
    os.chdir(prev_dir)

    return (rc, stdout, stderr)


# Traceback (ImportError)
ansible all -i inventory -m command -a "ansible --version"
localhost | FAILED | rc=1 >>
Traceback (most recent call last):
File "/Users/jladd/ansible/stable-2.1/bin/ansible", line 44, in <module>
import ansible.constants as C
ImportError: No module named constants

# Fixed code
def run_command(
    self,
    args,
    check_rc=False,
    close_fds=True,
    executable=None,
    data=None,
    binary_data=False,
    path_prefix=None,
    cwd=None,
    use_unsafe_shell=False,
    prompt_regex=None,
    environ_update=None,
):
    """
    Execute a command, returns rc, stdout, and stderr.

    :arg args: is the command to run
        * If args is a list, the command will be run with shell=False.
        * If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False
        * If args is a string and use_unsafe_shell=True it runs with shell=True.
    :kw check_rc: Whether to call fail_json in case of non zero RC.
        Default False
    :kw close_fds: See documentation for subprocess.Popen(). Default True
    :kw executable: See documentation for subprocess.Popen(). Default None
    :kw data: If given, information to write to the stdin of the command
    :kw binary_data: If False, append a newline to the data.  Default False
    :kw path_prefix: If given, additional path to find the command in.
        This adds to the PATH environment vairable so helper commands in
        the same directory can also be found
    :kw cwd: iIf given, working directory to run the command inside
    :kw use_unsafe_shell: See `args` parameter.  Default False
    :kw prompt_regex: Regex string (not a compiled regex) which can be
        used to detect prompts in the stdout which would otherwise cause
        the execution to hang (especially if no input data is specified)
    :kwarg environ_update: dictionary to *update* os.environ with
    """

    shell = False
    if isinstance(args, list):
        if use_unsafe_shell:
            args = " ".join([pipes.quote(x) for x in args])
            shell = True
    elif isinstance(args, basestring) and use_unsafe_shell:
        shell = True
    elif isinstance(args, basestring):
        if isinstance(args, unicode):
            args = args.encode("utf-8")
        args = shlex.split(args)
    else:
        msg = "Argument 'args' to run_command must be list or string"
        self.fail_json(rc=257, cmd=args, msg=msg)

    prompt_re = None
    if prompt_regex:
        try:
            prompt_re = re.compile(prompt_regex, re.MULTILINE)
        except re.error:
            self.fail_json(msg="invalid prompt regular expression given to run_command")

    # expand things like $HOME and ~
    if not shell:
        args = [
            os.path.expandvars(os.path.expanduser(x)) for x in args if x is not None
        ]

    rc = 0
    msg = None
    st_in = None

    # Manipulate the environ we'll send to the new process
    old_env_vals = {}
    # We can set this from both an attribute and per call
    for key, val in self.run_command_environ_update.items():
        old_env_vals[key] = os.environ.get(key, None)
        os.environ[key] = val
    if environ_update:
        for key, val in environ_update.items():
            old_env_vals[key] = os.environ.get(key, None)
            os.environ[key] = val
    if path_prefix:
        old_env_vals["PATH"] = os.environ["PATH"]
        os.environ["PATH"] = "%s:%s" % (path_prefix, os.environ["PATH"])

    # If using test-module and explode, the remote lib path will resemble ...
    #   /tmp/test_module_scratch/debug_dir/ansible/module_utils/basic.py
    # If using ansible or ansible-playbook with a remote system ...
    #   /tmp/ansible_vmweLQ/ansible_modlib.zip/ansible/module_utils/basic.py

    # Clean out python paths set by ziploader
    if "PYTHONPATH" in os.environ:
        pypaths = os.environ["PYTHONPATH"].split(":")
        pypaths = [
            x
            for x in pypaths
            if not x.endswith("/ansible_modlib.zip") and not x.endswith("/debug_dir")
        ]
        os.environ["PYTHONPATH"] = ":".join(pypaths)

    # create a printable version of the command for use
    # in reporting later, which strips out things like
    # passwords from the args list
    if isinstance(args, basestring):
        if isinstance(args, unicode):
            b_args = args.encode("utf-8")
        else:
            b_args = args
        to_clean_args = shlex.split(b_args)
        del b_args
    else:
        to_clean_args = args

    clean_args = []
    is_passwd = False
    for arg in to_clean_args:
        if is_passwd:
            is_passwd = False
            clean_args.append("********")
            continue
        if PASSWD_ARG_RE.match(arg):
            sep_idx = arg.find("=")
            if sep_idx > -1:
                clean_args.append("%s=********" % arg[:sep_idx])
                continue
            else:
                is_passwd = True
        arg = heuristic_log_sanitize(arg, self.no_log_values)
        clean_args.append(arg)
    clean_args = " ".join(pipes.quote(arg) for arg in clean_args)

    if data:
        st_in = subprocess.PIPE

    kwargs = dict(
        executable=executable,
        shell=shell,
        close_fds=close_fds,
        stdin=st_in,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )

    if cwd and os.path.isdir(cwd):
        kwargs["cwd"] = cwd

    # store the pwd
    prev_dir = os.getcwd()

    # make sure we're in the right working directory
    if cwd and os.path.isdir(cwd):
        try:
            os.chdir(cwd)
        except (OSError, IOError):
            e = get_exception()
            self.fail_json(rc=e.errno, msg="Could not open %s, %s" % (cwd, str(e)))

    try:
        if self._debug:
            if isinstance(args, list):
                running = " ".join(args)
            else:
                running = args
            self.log("Executing: " + running)
        cmd = subprocess.Popen(args, **kwargs)

        # the communication logic here is essentially taken from that
        # of the _communicate() function in ssh.py

        stdout = ""
        stderr = ""
        rpipes = [cmd.stdout, cmd.stderr]

        if data:
            if not binary_data:
                data += "\n"
            cmd.stdin.write(data)
            cmd.stdin.close()

        while True:
            rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
            if cmd.stdout in rfd:
                dat = os.read(cmd.stdout.fileno(), 9000)
                stdout += dat
                if dat == "":
                    rpipes.remove(cmd.stdout)
            if cmd.stderr in rfd:
                dat = os.read(cmd.stderr.fileno(), 9000)
                stderr += dat
                if dat == "":
                    rpipes.remove(cmd.stderr)
            # if we're checking for prompts, do it now
            if prompt_re:
                if prompt_re.search(stdout) and not data:
                    return (
                        257,
                        stdout,
                        "A prompt was encountered while running a command, but no input data was specified",
                    )
            # only break out if no pipes are left to read or
            # the pipes are completely read and
            # the process is terminated
            if (not rpipes or not rfd) and cmd.poll() is not None:
                break
            # No pipes are left to read but process is not yet terminated
            # Only then it is safe to wait for the process to be finished
            # NOTE: Actually cmd.poll() is always None here if rpipes is empty
            elif not rpipes and cmd.poll() == None:
                cmd.wait()
                # The process is terminated. Since no pipes to read from are
                # left, there is no need to call select() again.
                break

        cmd.stdout.close()
        cmd.stderr.close()

        rc = cmd.returncode
    except (OSError, IOError):
        e = get_exception()
        self.fail_json(rc=e.errno, msg=str(e), cmd=clean_args)
    except:
        self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)

    # Restore env settings
    for key, val in old_env_vals.items():
        if val is None:
            del os.environ[key]
        else:
            os.environ[key] = val

    if rc != 0 and check_rc:
        msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
        self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)

    # reset the pwd
    os.chdir(prev_dir)

    return (rc, stdout, stderr)