Launch the Ansible task configured in self.config (A RunnerConfig object), returns once the
        invocation is complete

def run(self):
        '''
        Launch the Ansible task configured in self.config (A RunnerConfig object), returns once the
        invocation is complete
        '''
        self.status_callback('starting')
        stdout_filename = os.path.join(self.config.artifact_dir, 'stdout')
        command_filename = os.path.join(self.config.artifact_dir, 'command')

        try:
            os.makedirs(self.config.artifact_dir, mode=0o700)
        except OSError as exc:
            if exc.errno == errno.EEXIST and os.path.isdir(self.config.artifact_dir):
                pass
            else:
                raise
        os.close(os.open(stdout_filename, os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR))

        command = [a.decode('utf-8') if six.PY2 else a for a in self.config.command]
        with codecs.open(command_filename, 'w', encoding='utf-8') as f:
            os.chmod(command_filename, stat.S_IRUSR | stat.S_IWUSR)
            json.dump(
                {'command': command,
                 'cwd': self.config.cwd,
                 'env': self.config.env}, f, ensure_ascii=False
            )

        if self.config.ident is not None:
            cleanup_artifact_dir(os.path.join(self.config.artifact_dir, ".."), self.config.rotate_artifacts)

        stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
        stdout_handle = OutputEventFilter(stdout_handle, self.event_callback, self.config.suppress_ansible_output, output_json=self.config.json_mode)

        if not isinstance(self.config.expect_passwords, collections.OrderedDict):
            # We iterate over `expect_passwords.keys()` and
            # `expect_passwords.values()` separately to map matched inputs to
            # patterns and choose the proper string to send to the subprocess;
            # enforce usage of an OrderedDict so that the ordering of elements in
            # `keys()` matches `values()`.
            expect_passwords = collections.OrderedDict(self.config.expect_passwords)
        password_patterns = list(expect_passwords.keys())
        password_values = list(expect_passwords.values())

        # pexpect needs all env vars to be utf-8 encoded bytes
        # https://github.com/pexpect/pexpect/issues/512

        # Use a copy so as not to cause problems when serializing the job_env.
        env = {
            ensure_str(k): ensure_str(v) if k != 'PATH' and isinstance(v, six.text_type) else v
            for k, v in self.config.env.items()
        }

        self.status_callback('running')
        self.last_stdout_update = time.time()
        try:
            child = pexpect.spawn(
                command[0],
                command[1:],
                cwd=self.config.cwd,
                env=env,
                ignore_sighup=True,
                encoding='utf-8',
                echo=False,
                use_poll=self.config.pexpect_use_poll,
            )
            child.logfile_read = stdout_handle
        except pexpect.exceptions.ExceptionPexpect as e:
            child = collections.namedtuple(
                'MissingProcess', 'exitstatus isalive'
            )(
                exitstatus=127,
                isalive=lambda: False
            )

            def _decode(x):
                return x.decode('utf-8') if six.PY2 else x

            # create the events directory (the callback plugin won't run, so it
            # won't get created)
            events_directory = os.path.join(self.config.artifact_dir, 'job_events')
            if not os.path.exists(events_directory):
                os.mkdir(events_directory, 0o700)
            stdout_handle.write(_decode(str(e)))
            stdout_handle.write(_decode('\n'))

        job_start = time.time()
        while child.isalive():
            result_id = child.expect(password_patterns,
                                     timeout=self.config.pexpect_timeout,
                                     searchwindowsize=100)
            password = password_values[result_id]
            if password is not None:
                child.sendline(password)
                self.last_stdout_update = time.time()
            if self.cancel_callback:
                try:
                    self.canceled = self.cancel_callback()
                except Exception as e:
                    # TODO: logger.exception('Could not check cancel callback - cancelling immediately')
                    #if isinstance(extra_update_fields, dict):
                    #    extra_update_fields['job_explanation'] = "System error during job execution, check system logs"
                    raise CallbackError("Exception in Cancel Callback: {}".format(e))
            if self.config.job_timeout and not self.canceled and (time.time() - job_start) > self.config.job_timeout:
                self.timed_out = True
                # if isinstance(extra_update_fields, dict):
                #     extra_update_fields['job_explanation'] = "Job terminated due to timeout"
            if self.canceled or self.timed_out or self.errored:
                Runner.handle_termination(child.pid, is_cancel=self.canceled)
            if self.config.idle_timeout and (time.time() - self.last_stdout_update) > self.config.idle_timeout:
                Runner.handle_termination(child.pid, is_cancel=False)
                self.timed_out = True

        stdout_handle.flush()
        stdout_handle.close()

        if self.canceled:
            self.status_callback('canceled')
        elif child.exitstatus == 0 and not self.timed_out:
            self.status_callback('successful')
        elif self.timed_out:
            self.status_callback('timeout')
        else:
            self.status_callback('failed')
        self.rc = child.exitstatus if not (self.timed_out or self.canceled) else 254
        for filename, data in [
            ('status', self.status),
            ('rc', self.rc),
        ]:
            artifact_path = os.path.join(self.config.artifact_dir, filename)
            if not os.path.exists(artifact_path):
                os.close(os.open(artifact_path, os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR))
            with open(artifact_path, 'w') as f:
                f.write(str(data))
        if self.config.directory_isolation_path and self.config.directory_isolation_cleanup:
            shutil.rmtree(self.config.directory_isolation_path)
        if self.finished_callback is not None:
            try:
                self.finished_callback(self)
            except Exception as e:
                raise CallbackError("Exception in Finished Callback: {}".format(e))
        return self.status, self.rc