# Buggy code
def run(self):
    """
    Execute the routine, the routine can be either:
    1. Execute a remote Salt command
    2. Execute a raw shell command
    3. Execute a wrapper func
    """
    if self.opts.get("raw_shell"):
        return self.shell.exec_cmd(self.arg_str)
    elif self.fun in self.wfuncs:
        # Ensure that opts/grains are up to date
        # Execute routine
        cdir = os.path.join(self.opts["cachedir"], "minions", self.id)
        if not os.path.isdir(cdir):
            os.makedirs(cdir)
        datap = os.path.join(cdir, "data.p")
        refresh = False
        if not os.path.isfile(datap):
            refresh = True
        else:
            passed_time = (time.time() - os.stat(datap).st_mtime) / 60
            if passed_time > self.opts.get("cache_life", 60):
                refresh = True
        if self.opts.get("refresh_cache"):
            refresh = True
        if refresh:
            # Make the datap
            # TODO: Auto expire the datap
            pre_wrapper = salt.client.ssh.wrapper.FunctionWrapper(
                self.opts, self.id, **self.target
            )
            opts_pkg = pre_wrapper["test.opts_pkg"]()
            pillar = salt.pillar.Pillar(
                opts_pkg,
                opts_pkg["grains"],
                opts_pkg["id"],
                opts_pkg.get("environment", "base"),
            )
            pillar_data = pillar.compile_pillar()

            # TODO: cache minion opts in datap in master.py
            with salt.utils.fopen(datap, "w+") as fp_:
                fp_.write(
                    self.serial.dumps(
                        {
                            "opts": opts_pkg,
                            "grains": opts_pkg["grains"],
                            "pillar": pillar_data,
                        }
                    )
                )
        with salt.utils.fopen(datap, "r") as fp_:
            data = self.serial.load(fp_)
        opts = data.get("opts", {})
        opts["grains"] = data.get("grains")
        opts["pillar"] = data.get("pillar")
        wrapper = salt.client.ssh.wrapper.FunctionWrapper(opts, self.id, **self.target)
        self.wfuncs = salt.loader.ssh_wrapper(opts, wrapper)
        wrapper.wfuncs = self.wfuncs
        ret = json.dumps(self.wfuncs[self.fun](*self.arg))
        return ret, ""
    else:
        return self.cmd_block()


# Traceback (AttributeError)
[ERROR   ] Error in function _master_opts:
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.7/salt/master.py", line 1444, in run_func
ret = getattr(self, func)(load)
File "/usr/lib/pymodules/python2.7/salt/master.py", line 891, in _master_opts
envs = self._file_envs()
File "/usr/lib/pymodules/python2.7/salt/fileserver/__init__.py", line 153, in envs
ret.update(self.servers[fstr]())
File "/usr/lib/pymodules/python2.7/salt/fileserver/hgfs.py", line 176, in envs
tags = repo.get_tags()
AttributeError: 'hgclient' object has no attribute 'get_tags'

# Fixed code
def run(self, deploy_attempted=False):
    """
    Execute the routine, the routine can be either:
    1. Execute a raw shell command
    2. Execute a wrapper func
    3. Execute a remote Salt command

    If a (re)deploy is needed, then retry the operation after a deploy
    attempt

    Returns tuple of (stdout, stderr)
    """

    stdout, stderr = None, None

    if self.opts.get("raw_shell"):
        stdout, stderr = self.shell.exec_cmd(self.arg_str)

    elif self.fun in self.wfuncs:
        stdout, stderr = self.run_wfunc()

    else:
        stdout, stderr = self.cmd_block()

    if stdout.startswith("deploy") and not deploy_attempted:
        self.deploy()
        return self.run(deploy_attempted=True)

    return stdout, stderr