diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 2c884802c2..1a6e0f25c1 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -478,6 +478,7 @@ class BaseTask(LogErrorsTask): model = None abstract = True cleanup_paths = [] + proot_show_paths = [] def update_model(self, pk, _attempt=0, **updates): """Reload the model instance from the database and update the @@ -793,6 +794,7 @@ class BaseTask(LogErrorsTask): # May have to serialize the value kwargs['private_data_files'] = self.build_private_data_files(instance, **kwargs) kwargs['passwords'] = self.build_passwords(instance, **kwargs) + kwargs['proot_show_paths'] = self.proot_show_paths args = self.build_args(instance, **kwargs) safe_args = self.build_safe_args(instance, **kwargs) output_replacements = self.build_output_replacements(instance, **kwargs) @@ -1288,6 +1290,10 @@ class RunProjectUpdate(BaseTask): name = 'awx.main.tasks.run_project_update' model = ProjectUpdate + @property + def proot_show_paths(self): + return [settings.PROJECTS_ROOT] + def build_private_data(self, project_update, **kwargs): ''' Return SSH private key data needed for this project update. @@ -1301,7 +1307,7 @@ class RunProjectUpdate(BaseTask): } } ''' - handle, self.revision_path = tempfile.mkstemp(dir=settings.AWX_PROOT_BASE_PATH) + handle, self.revision_path = tempfile.mkstemp(dir=settings.PROJECTS_ROOT) self.cleanup_paths.append(self.revision_path) private_data = {'credentials': {}} if project_update.credential: @@ -1594,6 +1600,12 @@ class RunProjectUpdate(BaseTask): if status == 'successful' and instance.launch_type != 'sync': self._update_dependent_inventories(instance, dependent_inventory_sources) + def should_use_proot(self, instance, **kwargs): + ''' + Return whether this task should use proot. + ''' + return getattr(settings, 'AWX_PROOT_ENABLED', False) + class RunInventoryUpdate(BaseTask): diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 99ad34d766..bbc52fad8d 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -181,6 +181,8 @@ class TestJobExecution: EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----' def setup_method(self, method): + if not os.path.exists(settings.PROJECTS_ROOT): + os.mkdir(settings.PROJECTS_ROOT) self.project_path = tempfile.mkdtemp(prefix='awx_project_') with open(os.path.join(self.project_path, 'helloworld.yml'), 'w') as f: f.write('---') @@ -281,6 +283,15 @@ class TestGenericRun(TestJobExecution): args, cwd, env, stdout = call_args assert args[0] == 'bwrap' + def test_bwrap_virtualenvs_are_readonly(self): + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert '--ro-bind %s %s' % (settings.ANSIBLE_VENV_PATH, settings.ANSIBLE_VENV_PATH) in ' '.join(args) # noqa + assert '--ro-bind %s %s' % (settings.AWX_VENV_PATH, settings.AWX_VENV_PATH) in ' '.join(args) # noqa + def test_awx_task_env(self): patch = mock.patch('awx.main.tasks.settings.AWX_TASK_ENV', {'FOO': 'BAR'}) patch.start() @@ -1096,6 +1107,27 @@ class TestProjectUpdateCredentials(TestJobExecution): ] } + def test_bwrap_exposes_projects_root(self): + ssh = CredentialType.defaults['ssh']() + self.instance.scm_type = 'git' + self.instance.credential = Credential( + pk=1, + credential_type=ssh, + ) + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, call_kwargs = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + + assert ' '.join(args).startswith('bwrap') + ' '.join([ + '--bind', + settings.PROJECTS_ROOT, + settings.PROJECTS_ROOT, + ]) in ' '.join(args) + assert '"scm_revision_output": "/projects/tmp' in ' '.join(args) + def test_username_and_password_auth(self, scm_type): ssh = CredentialType.defaults['ssh']() self.instance.scm_type = scm_type diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 58d795567f..c8c382472a 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -699,8 +699,13 @@ def wrap_args_with_proot(args, cwd, **kwargs): show_paths = [cwd, kwargs['private_data_dir']] else: show_paths = [cwd] - show_paths.extend([settings.ANSIBLE_VENV_PATH, settings.AWX_VENV_PATH]) + for venv in ( + settings.ANSIBLE_VENV_PATH, + settings.AWX_VENV_PATH + ): + new_args.extend(['--ro-bind', venv, venv]) show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or []) + show_paths.extend(kwargs.get('proot_show_paths', [])) for path in sorted(set(show_paths)): if not os.path.exists(path): continue