Use awx-plugins-shared code from awx_plugins.interfaces (#15566)

* Add `awx_plugins.interfaces` runtime dependency

* Use `awx_plugins.interfaces` for runtime detection

The original function name was `server_product_name()` but it didn't
really represent what it did. So it was renamed into
`detect_server_product_name()` in an attempt of disambiguation.

* Use `awx_plugins.interfaces` to map container path

The original function `to_container_path` has been renamed into
`get_incontainer_path()` to represent what it does better and make
the imports more obvious.

* Add license file for awx_plugins.interfaces

---------

Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com>
This commit is contained in:
Sviatoslav Sydorenko (Святослав Сидоренко)
2024-10-02 20:40:16 +02:00
committed by GitHub
parent 97ececa8b4
commit ece21b15d0
11 changed files with 236 additions and 49 deletions

View File

@@ -26,6 +26,9 @@ from django.utils.functional import cached_property
from django.utils.timezone import now
from django.contrib.auth.models import User
# Shared code for the AWX platform
from awx_plugins.interfaces._temporary_private_container_api import get_incontainer_path
# DRF
from awx.main.utils.pglock import advisory_lock
from rest_framework.serializers import ValidationError as DRFValidationError
@@ -41,7 +44,6 @@ from awx.main.fields import (
)
from awx.main.utils import decrypt_field, classproperty, set_environ
from awx.main.utils.safe_yaml import safe_dump
from awx.main.utils.execution_environments import to_container_path
from awx.main.validators import validate_ssh_private_key
from awx.main.models.base import CommonModelNameNotUnique, PasswordFieldsModel, PrimordialModel
from awx.main.models.mixins import ResourceMixin
@@ -623,7 +625,7 @@ class CredentialType(CommonModelNameNotUnique):
with open(path, 'w') as f:
f.write(data)
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
container_path = to_container_path(path, private_data_dir)
container_path = get_incontainer_path(path, private_data_dir)
# determine if filename indicates single file or many
if file_label.find('.') == -1:
@@ -665,7 +667,7 @@ class CredentialType(CommonModelNameNotUnique):
extra_vars = build_extra_vars(self.injectors.get('extra_vars', {}))
if extra_vars:
path = build_extra_vars_file(extra_vars, private_data_dir)
container_path = to_container_path(path, private_data_dir)
container_path = get_incontainer_path(path, private_data_dir)
args.extend(['-e', '@%s' % container_path])

View File

@@ -18,6 +18,9 @@ import urllib.parse as urlparse
# Django
from django.conf import settings
# Shared code for the AWX platform
from awx_plugins.interfaces._temporary_private_container_api import CONTAINER_ROOT, get_incontainer_path
# Runner
import ansible_runner
@@ -67,7 +70,6 @@ from awx.main.tasks.receptor import AWXReceptorJob
from awx.main.tasks.facts import start_fact_cache, finish_fact_cache
from awx.main.exceptions import AwxTaskError, PostRunError, ReceptorNodeNotFound
from awx.main.utils.ansible import read_ansible_config
from awx.main.utils.execution_environments import CONTAINER_ROOT, to_container_path
from awx.main.utils.safe_yaml import safe_dump, sanitize_jinja
from awx.main.utils.common import (
update_scm_url,
@@ -909,7 +911,7 @@ class RunJob(SourceControlMixin, BaseTask):
cred_files = private_data_files.get('credentials', {})
for cloud_cred in job.cloud_credentials:
if cloud_cred and cloud_cred.credential_type.namespace == 'openstack' and cred_files.get(cloud_cred, ''):
env['OS_CLIENT_CONFIG_FILE'] = to_container_path(cred_files.get(cloud_cred, ''), private_data_dir)
env['OS_CLIENT_CONFIG_FILE'] = get_incontainer_path(cred_files.get(cloud_cred, ''), private_data_dir)
for network_cred in job.network_credentials:
env['ANSIBLE_NET_USERNAME'] = network_cred.get_input('username', default='')
@@ -1552,7 +1554,7 @@ class RunInventoryUpdate(SourceControlMixin, BaseTask):
args.append('-i')
script_params = dict(hostvars=True, towervars=True)
source_inv_path = self.write_inventory_file(input_inventory, private_data_dir, f'hosts_{input_inventory.id}', script_params)
args.append(to_container_path(source_inv_path, private_data_dir))
args.append(get_incontainer_path(source_inv_path, private_data_dir))
# Include any facts from input inventories so they can be used in filters
start_fact_cache(
input_inventory.hosts.only(*HOST_FACTS_FIELDS),

View File

@@ -5,11 +5,12 @@ import json
import re
from collections import namedtuple
from awx_plugins.interfaces._temporary_private_container_api import get_incontainer_path
from awx.main.tasks.jobs import RunInventoryUpdate
from awx.main.models import InventorySource, Credential, CredentialType, UnifiedJob, ExecutionEnvironment
from awx.main.constants import CLOUD_PROVIDERS, STANDARD_INVENTORY_UPDATE_ENV
from awx.main.tests import data
from awx.main.utils.execution_environments import to_container_path
from django.conf import settings
@@ -115,7 +116,7 @@ def read_content(private_data_dir, raw_env, inventory_update):
continue # Ansible runner
abs_file_path = os.path.join(private_data_dir, filename)
file_aliases[abs_file_path] = filename
runner_path = to_container_path(abs_file_path, private_data_dir)
runner_path = get_incontainer_path(abs_file_path, private_data_dir)
if runner_path in inverse_env:
referenced_paths.add(abs_file_path)
alias = 'file_reference'
@@ -163,7 +164,7 @@ def read_content(private_data_dir, raw_env, inventory_update):
# assert that all files laid down are used
if (
abs_file_path not in referenced_paths
and to_container_path(abs_file_path, private_data_dir) not in inventory_content
and get_incontainer_path(abs_file_path, private_data_dir) not in inventory_content
and abs_file_path not in ignore_files
):
raise AssertionError(

View File

@@ -12,6 +12,8 @@ import pytest
import yaml
import jinja2
from awx_plugins.interfaces._temporary_private_container_api import CONTAINER_ROOT
from django.conf import settings
from awx.main.models import (
@@ -37,7 +39,6 @@ from awx.main.models.credential import HIDDEN_PASSWORD, ManagedCredentialType
from awx.main.tasks import jobs, system, receptor
from awx.main.utils import encrypt_field, encrypt_value
from awx.main.utils.safe_yaml import SafeLoader
from awx.main.utils.execution_environments import CONTAINER_ROOT
from awx.main.utils.licensing import Licenser
from awx.main.constants import JOB_VARIABLE_PREFIXES

View File

@@ -4,7 +4,7 @@ from uuid import uuid4
import pytest
from awx.main.utils.execution_environments import to_container_path
from awx_plugins.interfaces._temporary_private_container_api import get_incontainer_path
private_data_dir = '/tmp/pdd_iso/awx_xxx'
@@ -22,7 +22,7 @@ private_data_dir = '/tmp/pdd_iso/awx_xxx'
],
)
def test_switch_paths(container_path, host_path):
assert to_container_path(host_path, private_data_dir) == container_path
assert get_incontainer_path(host_path, private_data_dir) == container_path
def test_symlink_isolation_dir(request):
@@ -40,7 +40,7 @@ def test_symlink_isolation_dir(request):
pdd = f'{dst_path}/awx_xxx'
assert to_container_path(f'{pdd}/env/tmp1234', pdd) == '/runner/env/tmp1234'
assert get_incontainer_path(f'{pdd}/env/tmp1234', pdd) == '/runner/env/tmp1234'
@pytest.mark.parametrize(
@@ -53,4 +53,4 @@ def test_symlink_isolation_dir(request):
)
def test_invalid_host_path(host_path):
with pytest.raises(RuntimeError):
to_container_path(host_path, private_data_dir)
get_incontainer_path(host_path, private_data_dir)

View File

@@ -1,6 +1,4 @@
import os
import logging
from pathlib import Path
from django.conf import settings
@@ -51,24 +49,3 @@ def get_default_pod_spec():
],
},
}
# this is the root of the private data dir as seen from inside
# of the container running a job
CONTAINER_ROOT = '/runner'
def to_container_path(path, private_data_dir):
"""Given a path inside of the host machine filesystem,
this returns the expected path which would be observed by the job running
inside of the EE container.
This only handles the volume mount from private_data_dir to /runner
"""
if not os.path.isabs(private_data_dir):
raise RuntimeError('The private_data_dir path must be absolute')
# due to how tempfile.mkstemp works, we are probably passed a resolved path, but unresolved private_data_dir
resolved_path = Path(path).resolve()
resolved_pdd = Path(private_data_dir).resolve()
if resolved_pdd != resolved_path and resolved_pdd not in resolved_path.parents:
raise RuntimeError(f'Cannot convert path {resolved_path} unless it is a subdir of {resolved_pdd}')
return str(resolved_path).replace(str(resolved_pdd), CONTAINER_ROOT, 1)

View File

@@ -15,7 +15,6 @@ from datetime import datetime, timezone
import collections
import copy
import io
import os
import json
import logging
import re
@@ -35,6 +34,9 @@ from cryptography import x509
from django.conf import settings
from django.utils.translation import gettext_lazy as _
# Shared code for the AWX platform
from awx_plugins.interfaces._temporary_private_licensing_api import detect_server_product_name
from awx.main.constants import SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS
MAX_INSTANCES = 9999999
@@ -480,13 +482,9 @@ def get_licenser(*args, **kwargs):
from awx.main.utils.licensing import Licenser, OpenLicense
try:
if os.path.exists('/var/lib/awx/.tower_version'):
return Licenser(*args, **kwargs)
else:
if detect_server_product_name() == 'AWX':
return OpenLicense()
else:
return Licenser(*args, **kwargs)
except Exception as e:
raise ValueError(_('Error importing License: %s') % e)
def server_product_name():
return 'AWX' if isinstance(get_licenser(), OpenLicense) else 'Red Hat Ansible Automation Platform'