def handle(self, *args, **options): hostname = options.get('hostname') if not hostname: raise CommandError("--hostname is a required argument") try: path = tempfile.mkdtemp(prefix='awx_isolated_ssh', dir=settings.AWX_PROOT_BASE_PATH) args = [ 'ansible', 'all', '-i', '{},'.format(hostname), '-u', settings.AWX_ISOLATED_USERNAME, '-T5', '-m', 'shell', '-a', 'ansible-runner --version', '-vvv' ] if all([ getattr(settings, 'AWX_ISOLATED_KEY_GENERATION', False) is True, getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None) ]): ssh_key_path = os.path.join(path, '.isolated') ssh_auth_sock = os.path.join(path, 'ssh_auth.sock') run.open_fifo_write(ssh_key_path, settings.AWX_ISOLATED_PRIVATE_KEY) args = run.wrap_args_with_ssh_agent(args, ssh_key_path, ssh_auth_sock) try: print(' '.join(args)) subprocess.check_call(args) except subprocess.CalledProcessError as e: sys.exit(e.returncode) finally: shutil.rmtree(path)
def run_pexpect(cls, pexpect_args, *args, **kw): isolated_ssh_path = None try: if all([ getattr(settings, 'AWX_ISOLATED_KEY_GENERATION', False) is True, getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None) ]): isolated_ssh_path = tempfile.mkdtemp(prefix='awx_isolated', dir=settings.AWX_PROOT_BASE_PATH) os.chmod(isolated_ssh_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) isolated_key = os.path.join(isolated_ssh_path, '.isolated') ssh_sock = os.path.join(isolated_ssh_path, '.isolated_ssh_auth.sock') run.open_fifo_write(isolated_key, settings.AWX_ISOLATED_PRIVATE_KEY) pexpect_args = run.wrap_args_with_ssh_agent(pexpect_args, isolated_key, ssh_sock, silence_ssh_add=True) return run.run_pexpect(pexpect_args, *args, **kw) finally: if isolated_ssh_path: shutil.rmtree(isolated_ssh_path)
def dispatch(self): ''' Compile the playbook, its environment, and metadata into a series of files, and ship to a remote host for isolated execution. ''' self.started_at = time.time() secrets = { 'env': self.isolated_env, 'passwords': self.expect_passwords, 'ssh_key_data': None, 'idle_timeout': self.idle_timeout, 'job_timeout': self.job_timeout, 'pexpect_timeout': self.pexpect_timeout } # if an ssh private key fifo exists, read its contents and delete it if self.ssh_key_path: buff = StringIO.StringIO() with open(self.ssh_key_path, 'r') as fifo: for line in fifo: buff.write(line) secrets['ssh_key_data'] = buff.getvalue() os.remove(self.ssh_key_path) # write the entire secret payload to a named pipe # the run_isolated.yml playbook will use a lookup to read this data # into a variable, and will replicate the data into a named pipe on the # isolated instance secrets_path = os.path.join(self.private_data_dir, 'env') run.open_fifo_write(secrets_path, base64.b64encode(json.dumps(secrets))) self.build_isolated_job_data() extra_vars = { 'src': self.private_data_dir, 'dest': settings.AWX_PROOT_BASE_PATH, } if self.proot_temp_dir: extra_vars['proot_temp_dir'] = self.proot_temp_dir # Run ansible-playbook to launch a job on the isolated host. This: # # - sets up a temporary directory for proot/bwrap (if necessary) # - copies encrypted job data from the controlling host to the isolated host (with rsync) # - writes the encryption secret to a named pipe on the isolated host # - launches the isolated playbook runner via `awx-expect start <job-id>` args = self._build_args('run_isolated.yml', '%s,' % self.host, extra_vars) if self.instance.verbosity: args.append('-%s' % ('v' * min(5, self.instance.verbosity))) buff = StringIO.StringIO() logger.debug( 'Starting job {} on isolated host with `run_isolated.yml` playbook.' .format(self.instance.id)) status, rc = IsolatedManager.run_pexpect( args, self.awx_playbook_path(), self.management_env, buff, idle_timeout=self.idle_timeout, job_timeout=settings.AWX_ISOLATED_LAUNCH_TIMEOUT, pexpect_timeout=5) output = buff.getvalue().encode('utf-8') playbook_logger.info('Isolated job {} dispatch:\n{}'.format( self.instance.id, output)) if status != 'successful': self.stdout_handle.write(output) return status, rc