mirror of
https://github.com/ansible/awx.git
synced 2026-10-29 22:03:20 -02:30
AAP-85334: Normalize formatting with ruff format
Pure formatting commit — no logic changes. Run `ruff format` across awx/ and awxkit/ (awx_collection excluded).
This commit is contained in:
@@ -21,7 +21,7 @@ class LoggedBasicAuthentication(authentication.BasicAuthentication):
|
||||
ret = super(LoggedBasicAuthentication, self).authenticate(request)
|
||||
if ret:
|
||||
username = ret[0].username if ret[0] else '<none>'
|
||||
logger.info(smart_str(u"User {} performed a {} to {} through the API".format(username, request.method, request.path)))
|
||||
logger.info(smart_str("User {} performed a {} to {} through the API".format(username, request.method, request.path)))
|
||||
return ret
|
||||
|
||||
def authenticate_header(self, request):
|
||||
|
||||
+2
-3
@@ -81,7 +81,6 @@ analytics_logger = logging.getLogger('awx.analytics.performance')
|
||||
|
||||
|
||||
class LoggedLoginView(auth_views.LoginView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if is_proxied_request():
|
||||
next = request.GET.get('next', "")
|
||||
@@ -116,7 +115,7 @@ class LoggedLoginView(auth_views.LoginView):
|
||||
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
||||
ip = get_remote_host(request) # request.META.get('REMOTE_ADDR', None)
|
||||
if request.user.is_authenticated:
|
||||
logger.info(smart_str(u"User {} logged in from {}".format(self.request.user.username, ip)))
|
||||
logger.info(smart_str("User {} logged in from {}".format(self.request.user.username, ip)))
|
||||
ret.set_cookie(
|
||||
'userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False), samesite=getattr(settings, 'USER_COOKIE_SAMESITE', 'Lax')
|
||||
)
|
||||
@@ -125,7 +124,7 @@ class LoggedLoginView(auth_views.LoginView):
|
||||
return ret
|
||||
else:
|
||||
if 'username' in self.request.POST:
|
||||
logger.warning(smart_str(u"Login failed for user {} from {}".format(self.request.POST.get('username'), ip)))
|
||||
logger.warning(smart_str("Login failed for user {} from {}".format(self.request.POST.get('username'), ip)))
|
||||
ret.status_code = 401
|
||||
return ret
|
||||
|
||||
|
||||
+1
-2
@@ -171,8 +171,7 @@ class Metadata(metadata.SimpleMetadata):
|
||||
):
|
||||
field_info['type'] = 'json'
|
||||
elif (
|
||||
isinstance(field, ManyRelatedField)
|
||||
and field.field_name == 'credentials'
|
||||
isinstance(field, ManyRelatedField) and field.field_name == 'credentials'
|
||||
# launch-time credentials
|
||||
):
|
||||
field_info['type'] = 'list_of_ids'
|
||||
|
||||
+3
-3
@@ -99,9 +99,9 @@ class CustomAutoSchema(AutoSchema):
|
||||
except Exception:
|
||||
serializer = None
|
||||
warnings.warn(
|
||||
'{}.get_serializer() raised an exception during '
|
||||
'schema generation. Serializer fields will not be '
|
||||
'generated for this view.'.format(self.view.__class__.__name__)
|
||||
'{}.get_serializer() raised an exception during schema generation. Serializer fields will not be generated for this view.'.format(
|
||||
self.view.__class__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
if hasattr(self.view, 'swagger_topic'):
|
||||
|
||||
@@ -1292,9 +1292,9 @@ class ProjectOptionsSerializer(BaseSerializer):
|
||||
# Don't allow assigning a local_path when scm_type is set.
|
||||
valid_local_paths = Project.get_local_path_choices()
|
||||
if self.instance:
|
||||
scm_type = attrs.get('scm_type', self.instance.scm_type) or u''
|
||||
scm_type = attrs.get('scm_type', self.instance.scm_type) or ''
|
||||
else:
|
||||
scm_type = attrs.get('scm_type', u'') or u''
|
||||
scm_type = attrs.get('scm_type', '') or ''
|
||||
if self.instance and not scm_type:
|
||||
valid_local_paths.append(self.instance.local_path)
|
||||
if self.instance and scm_type and "local_path" in attrs and self.instance.local_path != attrs['local_path']:
|
||||
@@ -1914,7 +1914,7 @@ class HostSerializer(BaseSerializerWithVariables):
|
||||
if port < 1 or port > 65535:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
raise serializers.ValidationError(_(u'Invalid port specification: %s') % force_str(port))
|
||||
raise serializers.ValidationError(_('Invalid port specification: %s') % force_str(port))
|
||||
return name, port
|
||||
|
||||
def validate_name(self, value):
|
||||
@@ -3595,7 +3595,7 @@ class JobRelaunchSerializer(BaseSerializer):
|
||||
res = super(JobRelaunchSerializer, self).to_representation(obj)
|
||||
view = self.context.get('view', None)
|
||||
if hasattr(view, '_raw_data_form_marker'):
|
||||
password_keys = dict([(p, u'') for p in self.get_passwords_needed_to_start(obj)])
|
||||
password_keys = dict([(p, '') for p in self.get_passwords_needed_to_start(obj)])
|
||||
res.update(password_keys)
|
||||
return res
|
||||
|
||||
@@ -3765,7 +3765,7 @@ class AdHocCommandRelaunchSerializer(AdHocCommandSerializer):
|
||||
|
||||
def to_representation(self, obj):
|
||||
if obj:
|
||||
return dict([(p, u'') for p in obj.passwords_needed_to_start])
|
||||
return dict([(p, '') for p in obj.passwords_needed_to_start])
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
@@ -1768,11 +1768,15 @@ class CredentialExternalTest(OIDCCredentialTestMixin, SubDetailAPIView):
|
||||
obj_permission_type = 'use'
|
||||
resource_purpose = 'test external credential'
|
||||
|
||||
@extend_schema_if_available(extensions={"x-ai-description": """Test update the input values and metadata of an external credential.
|
||||
@extend_schema_if_available(
|
||||
extensions={
|
||||
"x-ai-description": """Test update the input values and metadata of an external credential.
|
||||
This endpoint supports testing credentials that connect to external secret management systems
|
||||
such as CyberArk AIM, CyberArk Conjur, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault,
|
||||
Centrify Vault, Thycotic DevOps Secrets Vault, and GitHub App Installation Access Token Lookup.
|
||||
It does not support standard credential types such as Machine, SCM, and Cloud."""})
|
||||
It does not support standard credential types such as Machine, SCM, and Cloud."""
|
||||
}
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
if obj.credential_type.kind != 'external':
|
||||
@@ -2692,11 +2696,11 @@ class JobTemplateLaunch(RetrieveAPIView):
|
||||
if needed_passwords:
|
||||
data['credential_passwords'] = {}
|
||||
for p in needed_passwords:
|
||||
data['credential_passwords'][p] = u''
|
||||
data['credential_passwords'][p] = ''
|
||||
else:
|
||||
data.pop('credential_passwords')
|
||||
for v in obj.variables_needed_to_start:
|
||||
extra_vars.setdefault(v, u'')
|
||||
extra_vars.setdefault(v, '')
|
||||
if extra_vars:
|
||||
data['extra_vars'] = extra_vars
|
||||
modified_ask_mapping = models.JobTemplate.get_ask_mapping()
|
||||
@@ -3347,7 +3351,7 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su
|
||||
'''
|
||||
relationships = ['success_nodes', 'failure_nodes', 'always_nodes']
|
||||
relationships.remove(self.relationship)
|
||||
qs = functools.reduce(lambda x, y: (x | y), (Q(**{'{}__in'.format(r): [sub.id]}) for r in relationships))
|
||||
qs = functools.reduce(lambda x, y: x | y, (Q(**{'{}__in'.format(r): [sub.id]}) for r in relationships))
|
||||
|
||||
if models.WorkflowJobTemplateNode.objects.filter(Q(pk=parent.id) & qs).exists():
|
||||
return {"Error": _("Relationship not allowed.")}
|
||||
@@ -3548,7 +3552,7 @@ class WorkflowJobTemplateLaunch(RetrieveAPIView):
|
||||
extra_vars = data.pop('extra_vars', None) or {}
|
||||
if obj:
|
||||
for v in obj.variables_needed_to_start:
|
||||
extra_vars.setdefault(v, u'')
|
||||
extra_vars.setdefault(v, '')
|
||||
if extra_vars:
|
||||
data['extra_vars'] = extra_vars
|
||||
modified_ask_mapping = models.WorkflowJobTemplate.get_ask_mapping()
|
||||
@@ -3958,7 +3962,7 @@ class JobRelaunch(RetrieveAPIView):
|
||||
if needed_passwords:
|
||||
data['credential_passwords'] = {}
|
||||
for p in needed_passwords:
|
||||
data['credential_passwords'][p] = u''
|
||||
data['credential_passwords'][p] = ''
|
||||
else:
|
||||
data.pop('credential_passwords', None)
|
||||
return data
|
||||
|
||||
@@ -246,7 +246,7 @@ class ApiV2SubscriptionView(APIView):
|
||||
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
||||
msg = exc.args[0]
|
||||
else:
|
||||
logger.exception(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
logger.exception(smart_str("Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response(validated)
|
||||
@@ -298,7 +298,7 @@ class ApiV2AttachView(APIView):
|
||||
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
||||
msg = exc.args[0]
|
||||
else:
|
||||
logger.exception(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
logger.exception(smart_str("Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
for sub in validated:
|
||||
@@ -381,7 +381,7 @@ class ApiV2ConfigView(APIView):
|
||||
try:
|
||||
data_actual = json.dumps(request.data)
|
||||
except Exception:
|
||||
logger.info(smart_str(u"Invalid JSON submitted for license."), extra=dict(actor=request.user.username))
|
||||
logger.info(smart_str("Invalid JSON submitted for license."), extra=dict(actor=request.user.username))
|
||||
return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
license_data = json.loads(data_actual)
|
||||
@@ -406,7 +406,7 @@ class ApiV2ConfigView(APIView):
|
||||
license_data_validated = get_licenser().license_from_manifest(license_data)
|
||||
connection.on_commit(lambda: clear_setting_cache.delay(['LICENSE']))
|
||||
except Exception:
|
||||
logger.warning(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
logger.warning(smart_str("Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
license_data_validated = get_licenser().validate()
|
||||
@@ -417,7 +417,7 @@ class ApiV2ConfigView(APIView):
|
||||
settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host())
|
||||
return Response(license_data_validated)
|
||||
|
||||
logger.warning(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
logger.warning(smart_str("Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||
return Response({"error": _("Invalid subscription")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@extend_schema_if_available(
|
||||
|
||||
+3
-3
@@ -27,11 +27,11 @@ class Setting(CreatedModifiedModel):
|
||||
json_value = json.dumps(self.value)
|
||||
except ValueError:
|
||||
# In the rare case the DB value is invalid JSON.
|
||||
json_value = u'<Invalid JSON>'
|
||||
json_value = '<Invalid JSON>'
|
||||
if self.user:
|
||||
return u'{} ({}) = {}'.format(self.key, self.user, json_value)
|
||||
return '{} ({}) = {}'.format(self.key, self.user, json_value)
|
||||
else:
|
||||
return u'{} = {}'.format(self.key, json_value)
|
||||
return '{} = {}'.format(self.key, json_value)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
encrypted = settings_registry.is_setting_encrypted(self.key)
|
||||
|
||||
@@ -53,8 +53,9 @@ def test_non_admin_user_does_not_see_categories(api_request, dummy_setting, norm
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_setting_singleton_detail_retrieve(api_request, dummy_setting):
|
||||
with dummy_setting('FOO_BAR_1', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'), dummy_setting(
|
||||
'FOO_BAR_2', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'
|
||||
with (
|
||||
dummy_setting('FOO_BAR_1', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'),
|
||||
dummy_setting('FOO_BAR_2', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'),
|
||||
):
|
||||
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}))
|
||||
assert response.status_code == 200
|
||||
@@ -64,8 +65,9 @@ def test_setting_singleton_detail_retrieve(api_request, dummy_setting):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_setting_singleton_detail_invalid_retrieve(api_request, dummy_setting, normal_user):
|
||||
with dummy_setting('FOO_BAR_1', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'), dummy_setting(
|
||||
'FOO_BAR_2', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'
|
||||
with (
|
||||
dummy_setting('FOO_BAR_1', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'),
|
||||
dummy_setting('FOO_BAR_2', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'),
|
||||
):
|
||||
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'barfoo'}))
|
||||
assert response.status_code == 404
|
||||
@@ -104,8 +106,9 @@ def test_setting_singleton_update(api_request, dummy_setting):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_setting_singleton_update_dont_change_readonly_fields(api_request, dummy_setting):
|
||||
with dummy_setting('FOO_BAR', field_class=fields.IntegerField, read_only=True, default=4, category='FooBar', category_slug='foobar'), mock.patch(
|
||||
'awx.conf.views.clear_setting_cache'
|
||||
with (
|
||||
dummy_setting('FOO_BAR', field_class=fields.IntegerField, read_only=True, default=4, category='FooBar', category_slug='foobar'),
|
||||
mock.patch('awx.conf.views.clear_setting_cache'),
|
||||
):
|
||||
api_request('patch', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}), data={'FOO_BAR': 5})
|
||||
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}))
|
||||
@@ -114,8 +117,9 @@ def test_setting_singleton_update_dont_change_readonly_fields(api_request, dummy
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_setting_singleton_update_dont_change_encrypted_mark(api_request, dummy_setting):
|
||||
with dummy_setting('FOO_BAR', field_class=fields.CharField, encrypted=True, category='FooBar', category_slug='foobar'), mock.patch(
|
||||
'awx.conf.views.clear_setting_cache'
|
||||
with (
|
||||
dummy_setting('FOO_BAR', field_class=fields.CharField, encrypted=True, category='FooBar', category_slug='foobar'),
|
||||
mock.patch('awx.conf.views.clear_setting_cache'),
|
||||
):
|
||||
api_request('patch', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}), data={'FOO_BAR': 'password'})
|
||||
assert Setting.objects.get(key='FOO_BAR').value.startswith('$encrypted$')
|
||||
@@ -132,9 +136,11 @@ def test_setting_singleton_update_runs_custom_validate(api_request, dummy_settin
|
||||
def func_raising_exception(serializer, attrs):
|
||||
raise serializers.ValidationError('Error')
|
||||
|
||||
with dummy_setting('FOO_BAR', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'), dummy_validate(
|
||||
'foobar', func_raising_exception
|
||||
), mock.patch('awx.conf.views.clear_setting_cache'):
|
||||
with (
|
||||
dummy_setting('FOO_BAR', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'),
|
||||
dummy_validate('foobar', func_raising_exception),
|
||||
mock.patch('awx.conf.views.clear_setting_cache'),
|
||||
):
|
||||
response = api_request('patch', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}), data={'FOO_BAR': 23})
|
||||
assert response.status_code == 400
|
||||
|
||||
@@ -149,8 +155,9 @@ def test_setting_singleton_delete(api_request, dummy_setting):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_setting_singleton_delete_no_read_only_fields(api_request, dummy_setting):
|
||||
with dummy_setting('FOO_BAR', field_class=fields.IntegerField, read_only=True, default=23, category='FooBar', category_slug='foobar'), mock.patch(
|
||||
'awx.conf.views.clear_setting_cache'
|
||||
with (
|
||||
dummy_setting('FOO_BAR', field_class=fields.IntegerField, read_only=True, default=23, category='FooBar', category_slug='foobar'),
|
||||
mock.patch('awx.conf.views.clear_setting_cache'),
|
||||
):
|
||||
api_request('delete', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}))
|
||||
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}))
|
||||
|
||||
@@ -227,7 +227,7 @@ def counts(since, **kwargs):
|
||||
counts['pending_jobs'] = models.UnifiedJob.objects.exclude(launch_type='sync').filter(status__in=('pending',)).count()
|
||||
if connection.vendor == 'postgresql':
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f"select count(*) from pg_stat_activity where datname=\'{connection.settings_dict['NAME']}\'")
|
||||
cursor.execute(f"select count(*) from pg_stat_activity where datname='{connection.settings_dict['NAME']}'")
|
||||
counts['database_connections'] = cursor.fetchone()[0]
|
||||
else:
|
||||
# We should be using postgresql, but if we do that change that ever we should change the below value
|
||||
@@ -441,7 +441,7 @@ def _events_table(since, full_path, until, tbl, where_column, project_job_create
|
||||
WHERE ({tbl}.{where_column} > '{since.isoformat()}' AND {tbl}.{where_column} <= '{until.isoformat()}')) TO STDOUT WITH CSV HEADER'''
|
||||
return query
|
||||
|
||||
return _copy_table(table='events', query=query(fr"replace({tbl}.event_data, '\u', '\u005cu')::jsonb"), path=full_path)
|
||||
return _copy_table(table='events', query=query(rf"replace({tbl}.event_data, '\u', '\u005cu')::jsonb"), path=full_path)
|
||||
|
||||
|
||||
@register('events_table', '1.5', format='csv', description=_('Automation task records'), expensive=four_hour_slicing)
|
||||
|
||||
@@ -181,7 +181,9 @@ class HistogramM(BaseM):
|
||||
node_label = f'node="{instance}"'
|
||||
subsystem_label = f',subsystem="{namespace}"' if namespace else ''
|
||||
for i, b in enumerate(self.buckets):
|
||||
output_text += f'{self.field}_bucket{{le="{b}",{node_label}{subsystem_label}}} {sum(instance_data[instance][self.field]["counts"][0:i+1])}\n'
|
||||
output_text += (
|
||||
f'{self.field}_bucket{{le="{b}",{node_label}{subsystem_label}}} {sum(instance_data[instance][self.field]["counts"][0 : i + 1])}\n'
|
||||
)
|
||||
output_text += f'{self.field}_bucket{{le="+Inf",{node_label}{subsystem_label}}} {instance_data[instance][self.field]["inf"]}\n'
|
||||
output_text += f'{self.field}_count{{{node_label}{subsystem_label}}} {instance_data[instance][self.field]["inf"]}\n'
|
||||
output_text += f'{self.field}_sum{{{node_label}{subsystem_label}}} {instance_data[instance][self.field]["sum"]}\n'
|
||||
|
||||
+1
-2
@@ -39,8 +39,7 @@ class MainConfig(AppConfig):
|
||||
read_only=True,
|
||||
label=_('List of all named url graph nodes.'),
|
||||
help_text=_(
|
||||
'Read-only list of key-value pairs that exposes named URL graph topology.'
|
||||
' Use this list to programmatically generate named URLs for resources'
|
||||
'Read-only list of key-value pairs that exposes named URL graph topology. Use this list to programmatically generate named URLs for resources'
|
||||
),
|
||||
category=_('Named URL'),
|
||||
category_slug='named-url',
|
||||
|
||||
+2
-4
@@ -488,7 +488,7 @@ register(
|
||||
min_value=0,
|
||||
label=_('Job Event Standard Output Maximum Display Size'),
|
||||
help_text=_(
|
||||
u'Maximum Size of Standard Output in bytes to display for a single job or ad hoc command event. `stdout` will end with `\u2026` when truncated.'
|
||||
'Maximum Size of Standard Output in bytes to display for a single job or ad hoc command event. `stdout` will end with `\u2026` when truncated.'
|
||||
),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs',
|
||||
@@ -722,9 +722,7 @@ register(
|
||||
default='https',
|
||||
label=_('Logging Aggregator Protocol'),
|
||||
help_text=_(
|
||||
'Protocol used to communicate with log aggregator. '
|
||||
'HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in '
|
||||
'the Logging Aggregator hostname.'
|
||||
'Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in the Logging Aggregator hostname.'
|
||||
),
|
||||
category=_('Logging'),
|
||||
category_slug='logging',
|
||||
|
||||
@@ -20,7 +20,6 @@ def signame(sig):
|
||||
|
||||
|
||||
class AWXConsumerRedis(object):
|
||||
|
||||
def __init__(self, name, worker):
|
||||
self.name = name
|
||||
self.pool = WorkerPool()
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import connection
|
||||
|
||||
|
||||
class AWXTaskWorker(TaskWorker):
|
||||
|
||||
def on_start(self) -> None:
|
||||
"""Get worker connected so that first task it gets will be worked quickly"""
|
||||
connection.ensure_connection()
|
||||
|
||||
@@ -20,7 +20,7 @@ class Command(BaseCommand):
|
||||
"""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT pg_index.indisvalid " "FROM pg_class JOIN pg_index ON pg_class.oid = pg_index.indexrelid " "WHERE pg_class.relname = %s",
|
||||
"SELECT pg_index.indisvalid FROM pg_class JOIN pg_index ON pg_class.oid = pg_index.indexrelid WHERE pg_class.relname = %s",
|
||||
[self.INDEX_NAME],
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
@@ -52,7 +52,7 @@ class Command(BaseCommand):
|
||||
# have set ALTER ROLE ... SET statement_timeout which would kill
|
||||
# the index build on large tables.
|
||||
cursor.execute("SET statement_timeout = 0")
|
||||
cursor.execute(f"CREATE INDEX CONCURRENTLY {self.INDEX_NAME} " f"ON {self.TABLE_NAME} (host_id, id DESC)")
|
||||
cursor.execute(f"CREATE INDEX CONCURRENTLY {self.INDEX_NAME} ON {self.TABLE_NAME} (host_id, id DESC)")
|
||||
finally:
|
||||
connection.connection.autocommit = old_autocommit
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ class Command(BaseCommand):
|
||||
with tarfile.open(tar_filename, "w") as tar:
|
||||
for cis in CustomInventoryScript.objects.all():
|
||||
# naming convention similar to project paths
|
||||
slug_name = slugify(str(cis.name)).replace(u'-', u'_')
|
||||
script_filename = u'_%d__%s' % (int(cis.pk), slug_name)
|
||||
slug_name = slugify(str(cis.name)).replace('-', '_')
|
||||
script_filename = '_%d__%s' % (int(cis.pk), slug_name)
|
||||
script_path = os.path.join(tmpdirname, script_filename)
|
||||
|
||||
with open(script_path, 'w') as f:
|
||||
|
||||
@@ -43,9 +43,7 @@ class Command(BaseCommand):
|
||||
'hosts_added',
|
||||
'hosts_deleted',
|
||||
'indirectly_managed_hosts',
|
||||
).order_by(
|
||||
'date'
|
||||
)[offset : offset + limit]
|
||||
).order_by('date')[offset : offset + limit]
|
||||
)
|
||||
|
||||
return list_of_queryset
|
||||
@@ -90,8 +88,8 @@ class Command(BaseCommand):
|
||||
|
||||
def csv_for_tar(self, temp_dir, type, filter_kwargs, rows_per_file, always_header=True):
|
||||
for index, list_of_queryset in enumerate(self.paginated_db_retrieval(type, filter_kwargs, rows_per_file)):
|
||||
csv_file = f'{temp_dir}/{type}{index+1}.csv'
|
||||
arcname_file = f'{type}{index+1}.csv'
|
||||
csv_file = f'{temp_dir}/{type}{index + 1}.csv'
|
||||
arcname_file = f'{type}{index + 1}.csv'
|
||||
|
||||
first_write = True if index == 0 else False
|
||||
|
||||
|
||||
@@ -114,9 +114,15 @@ class Command(BaseCommand):
|
||||
parser.add_argument(
|
||||
'--hostnames', dest='hostnames', type=str, help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--instance_percent', dest='instance_percent', type=int, default=0, help='The percentage of active instances that will be assigned to this group'
|
||||
),
|
||||
(
|
||||
parser.add_argument(
|
||||
'--instance_percent',
|
||||
dest='instance_percent',
|
||||
type=int,
|
||||
default=0,
|
||||
help='The percentage of active instances that will be assigned to this group',
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--instance_minimum',
|
||||
dest='instance_minimum',
|
||||
|
||||
@@ -42,9 +42,7 @@ def migrate_event_data(apps, schema_editor):
|
||||
|
||||
# create parent table
|
||||
cursor.execute(
|
||||
f'CREATE TABLE {tblname} '
|
||||
f'(LIKE tmp_{tblname} INCLUDING ALL, job_created TIMESTAMP WITH TIME ZONE NOT NULL) '
|
||||
f'PARTITION BY RANGE(job_created);'
|
||||
f'CREATE TABLE {tblname} (LIKE tmp_{tblname} INCLUDING ALL, job_created TIMESTAMP WITH TIME ZONE NOT NULL) PARTITION BY RANGE(job_created);'
|
||||
)
|
||||
|
||||
cursor.execute(f'DROP TABLE tmp_{tblname}')
|
||||
|
||||
@@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0195_EE_permissions'),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0196_indirect_managed_node_audit'),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('main', '0197_add_opa_query_path')]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0198_alter_inventorysource_source_and_more'),
|
||||
]
|
||||
|
||||
@@ -31,7 +31,6 @@ def rename_wfjt(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0199_inventorygroupvariableswithhistory_and_more'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ logger = logging.getLogger('awx.main.migrations')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0202_convert_controller_role_definitions'),
|
||||
]
|
||||
|
||||
@@ -37,7 +37,7 @@ class Migration(migrations.Migration):
|
||||
# delete all sso application content permissions
|
||||
# Added reverse_sql=migrations.RunSQL.noop to make this reversible for tests
|
||||
migrations.RunSQL(
|
||||
"DELETE FROM auth_permission " "WHERE content_type_id IN (SELECT id FROM django_content_type WHERE app_label = 'sso');",
|
||||
"DELETE FROM auth_permission WHERE content_type_id IN (SELECT id FROM django_content_type WHERE app_label = 'sso');",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
# delete sso application content type
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0204_squashed_deletions'),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0206_jobhostsummary_host_id_idx'),
|
||||
]
|
||||
|
||||
@@ -93,7 +93,7 @@ class ActivityStream(models.Model):
|
||||
timestamp = self.timestamp
|
||||
else:
|
||||
timestamp = '_delayed_'
|
||||
return u'%s-%s-pk=%s' % (operation, timestamp, self.pk)
|
||||
return '%s-%s-pk=%s' % (operation, timestamp, self.pk)
|
||||
|
||||
def get_absolute_url(self, request=None):
|
||||
return reverse('api:activity_stream_detail', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
@@ -221,7 +221,7 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
|
||||
self.preferred_instance_groups_cache = self._get_preferred_instance_group_cache()
|
||||
add_to_update_fields("preferred_instance_groups_cache")
|
||||
if not self.name:
|
||||
self.name = Truncator(u': '.join(filter(None, (self.module_name, self.module_args)))).chars(512)
|
||||
self.name = Truncator(': '.join(filter(None, (self.module_name, self.module_args)))).chars(512)
|
||||
add_to_update_fields("name")
|
||||
if self.task_impact == 0:
|
||||
self.task_impact = self._get_task_impact()
|
||||
|
||||
@@ -102,9 +102,9 @@ class BaseModel(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
if 'name' in self.__dict__:
|
||||
return u'%s-%s' % (self.name, self.pk)
|
||||
return '%s-%s' % (self.name, self.pk)
|
||||
else:
|
||||
return u'%s-%s' % (self._meta.verbose_name, self.pk)
|
||||
return '%s-%s' % (self._meta.verbose_name, self.pk)
|
||||
|
||||
def clean_fields(self, exclude=None):
|
||||
"""
|
||||
|
||||
@@ -525,7 +525,7 @@ class JobEvent(BasePlaybookEvent):
|
||||
return reverse('api:job_event_detail', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
def __str__(self):
|
||||
return u'%s @ %s' % (self.get_event_display2(), self.created.isoformat())
|
||||
return '%s @ %s' % (self.get_event_display2(), self.created.isoformat())
|
||||
|
||||
def _hostnames(self):
|
||||
hostnames = set()
|
||||
@@ -717,7 +717,7 @@ class BaseCommandEvent(CreatedModifiedModel):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return u'%s @ %s' % (self.get_event_display(), self.created.isoformat())
|
||||
return '%s @ %s' % (self.get_event_display(), self.created.isoformat())
|
||||
|
||||
@classmethod
|
||||
def create_from_data(cls, **kwargs):
|
||||
|
||||
@@ -701,7 +701,7 @@ class WebhookMixin(models.Model):
|
||||
if response.status_code < 400:
|
||||
logger.debug("Webhook status update sent.")
|
||||
else:
|
||||
logger.error("Posting webhook status failed, code: {}\n" "{}\nPayload sent: {}".format(response.status_code, response.text, json.dumps(data)))
|
||||
logger.error("Posting webhook status failed, code: {}\n{}\nPayload sent: {}".format(response.status_code, response.text, json.dumps(data)))
|
||||
|
||||
|
||||
class OpaQueryPathMixin(models.Model):
|
||||
|
||||
@@ -371,8 +371,8 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEn
|
||||
self._skip_update = bool(kwargs.pop('skip_update', False))
|
||||
# Create auto-generated local path if project uses SCM.
|
||||
if self.pk and self.scm_type and not self.local_path.startswith('_'):
|
||||
slug_name = slugify(str(self.name)).replace(u'-', u'_')
|
||||
self.local_path = u'_%d__%s' % (int(self.pk), slug_name)
|
||||
slug_name = slugify(str(self.name)).replace('-', '_')
|
||||
self.local_path = '_%d__%s' % (int(self.pk), slug_name)
|
||||
if 'local_path' not in update_fields:
|
||||
update_fields.append('local_path')
|
||||
# Do the actual save.
|
||||
|
||||
+12
-6
@@ -180,9 +180,9 @@ class Role(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
if 'role_field' in self.__dict__:
|
||||
return u'%s-%s' % (self.name, self.pk)
|
||||
return '%s-%s' % (self.name, self.pk)
|
||||
else:
|
||||
return u'%s-%s' % (self._meta.verbose_name, self.pk)
|
||||
return '%s-%s' % (self._meta.verbose_name, self.pk)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Role, self).save(*args, **kwargs)
|
||||
@@ -365,7 +365,8 @@ class Role(models.Model):
|
||||
if len(removals) > 0:
|
||||
for ids in split_ids_for_sqlite(removals):
|
||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
'''
|
||||
DELETE FROM %(ancestors_table)s
|
||||
WHERE descendent_id IN (%(ids)s)
|
||||
AND descendent_id != ancestor_id
|
||||
@@ -377,7 +378,9 @@ class Role(models.Model):
|
||||
WHERE parents.from_role_id = %(ancestors_table)s.descendent_id
|
||||
AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id
|
||||
)
|
||||
''' % sql_params)
|
||||
'''
|
||||
% sql_params
|
||||
)
|
||||
|
||||
delete_ct += cursor.rowcount
|
||||
|
||||
@@ -385,7 +388,8 @@ class Role(models.Model):
|
||||
if len(additions) > 0:
|
||||
for ids in split_ids_for_sqlite(additions):
|
||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||
cursor.execute('''
|
||||
cursor.execute(
|
||||
'''
|
||||
INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id)
|
||||
SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM (
|
||||
SELECT roles.id from_id,
|
||||
@@ -415,7 +419,9 @@ class Role(models.Model):
|
||||
AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id
|
||||
)
|
||||
|
||||
''' % sql_params)
|
||||
'''
|
||||
% sql_params
|
||||
)
|
||||
insert_ct += cursor.rowcount
|
||||
|
||||
if insert_ct == 0 and delete_ct == 0:
|
||||
|
||||
@@ -276,7 +276,7 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
|
||||
return rruleset
|
||||
|
||||
def __str__(self):
|
||||
return u'%s_t%s_%s_%s' % (self.name, self.unified_job_template.id, self.id, self.next_run)
|
||||
return '%s_t%s_%s_%s' % (self.name, self.unified_job_template.id, self.id, self.next_run)
|
||||
|
||||
def get_absolute_url(self, request=None):
|
||||
return reverse('api:schedule_detail', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
@@ -836,7 +836,7 @@ class UnifiedJob(
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return u'%s-%s-%s' % (self.created, self.id, self.status)
|
||||
return '%s-%s-%s' % (self.created, self.id, self.status)
|
||||
|
||||
@property
|
||||
def log_format(self):
|
||||
@@ -1416,14 +1416,14 @@ class UnifiedJob(
|
||||
|
||||
if not all(opts.values()):
|
||||
missing_fields = ', '.join([k for k, v in opts.items() if not v])
|
||||
self.job_explanation = u'Missing needed fields: %s.' % missing_fields
|
||||
self.job_explanation = 'Missing needed fields: %s.' % missing_fields
|
||||
self.save(update_fields=['job_explanation'])
|
||||
|
||||
return opts
|
||||
|
||||
def pre_start(self):
|
||||
if not self.can_start:
|
||||
self.job_explanation = u'%s is not in a startable state: %s, expecting one of %s' % (self._meta.verbose_name, self.status, str(('new', 'waiting')))
|
||||
self.job_explanation = '%s is not in a startable state: %s, expecting one of %s' % (self._meta.verbose_name, self.status, str(('new', 'waiting')))
|
||||
self.save(update_fields=['job_explanation'])
|
||||
return (False, None)
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class WebhookBackend(AWXBaseEmailBackend, CustomNotificationBase):
|
||||
url_next_log_safe = base64.b64encode(url_next.encode('UTF-8')) if url_next else b'None'
|
||||
|
||||
# we've hit a redirect. extract the redirect URL out of the first response header and try again
|
||||
logger.warning(f"Received a {resp.status_code} from {url_log_safe}, trying to reach redirect url {url_next_log_safe}; attempt #{retries+1}")
|
||||
logger.warning(f"Received a {resp.status_code} from {url_log_safe}, trying to reach redirect url {url_next_log_safe}; attempt #{retries + 1}")
|
||||
|
||||
# take the first redirect URL in the response header and try that
|
||||
url = url_next
|
||||
|
||||
@@ -256,8 +256,7 @@ class WorkflowManager(TaskBase):
|
||||
)
|
||||
display_list = [spawn_node.unified_job_template] + workflow_ancestors
|
||||
job.job_explanation = gettext_noop(
|
||||
"Workflow Job spawned from workflow could not start because it "
|
||||
"would result in recursion (spawn order, most recent first: {})"
|
||||
"Workflow Job spawned from workflow could not start because it would result in recursion (spawn order, most recent first: {})"
|
||||
).format(', '.join('<{}>'.format(tmp) for tmp in display_list))
|
||||
else:
|
||||
logger.debug(
|
||||
@@ -344,7 +343,7 @@ class DependencyManager(TaskBase):
|
||||
if update.status in ['waiting', 'pending', 'running']:
|
||||
return False
|
||||
|
||||
return bool(((update.finished + timedelta(seconds=cache_timeout))) < tz_now())
|
||||
return bool((update.finished + timedelta(seconds=cache_timeout)) < tz_now())
|
||||
|
||||
def get_or_create_project_update(self, project_id):
|
||||
project = self.all_projects.get(project_id, None)
|
||||
|
||||
@@ -563,7 +563,7 @@ class BaseTask(object):
|
||||
project.save()
|
||||
lock_path = project.get_lock_file()
|
||||
if lock_path is None:
|
||||
raise RuntimeError(u'Invalid lock file path')
|
||||
raise RuntimeError('Invalid lock file path')
|
||||
|
||||
try:
|
||||
self.lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT)
|
||||
|
||||
@@ -180,7 +180,7 @@ def find_node_in_mesh(node_name, receptor_ctl):
|
||||
if node.get('NodeID') == node_name:
|
||||
return node
|
||||
else:
|
||||
logger.warning(f"Instance {node_name} is not in the receptor mesh. {attempts-attempt} attempts left.")
|
||||
logger.warning(f"Instance {node_name} is not in the receptor mesh. {attempts - attempt} attempts left.")
|
||||
time.sleep(backoff)
|
||||
backoff += 1
|
||||
else:
|
||||
@@ -848,7 +848,7 @@ def reload_receptor():
|
||||
receptor_ctl.simple_command("reload")
|
||||
break
|
||||
except ValueError:
|
||||
logger.warning(f"Unable to reload Receptor configuration. {attempts-backoff} attempts left.")
|
||||
logger.warning(f"Unable to reload Receptor configuration. {attempts - backoff} attempts left.")
|
||||
time.sleep(backoff)
|
||||
else:
|
||||
raise RuntimeError("Receptor reload failed")
|
||||
|
||||
@@ -79,7 +79,7 @@ from awx.main.utils.reload import stop_local_services
|
||||
|
||||
logger = logging.getLogger('awx.main.tasks.system')
|
||||
|
||||
OPENSSH_KEY_ERROR = u'''\
|
||||
OPENSSH_KEY_ERROR = '''\
|
||||
It looks like you're trying to use a private key in OpenSSH format, which \
|
||||
isn't supported by the installed version of OpenSSH on this instance. \
|
||||
Try upgrading OpenSSH or providing your private key in an different format. \
|
||||
@@ -690,7 +690,6 @@ def _get_active_task_ids_from_dispatcherd(binder):
|
||||
"""
|
||||
active_task_ids = []
|
||||
try:
|
||||
|
||||
logger.debug("Querying dispatcherd API for running tasks")
|
||||
data = binder.control('running')
|
||||
|
||||
@@ -934,8 +933,10 @@ def awx_periodic_scheduler():
|
||||
continue
|
||||
if not can_start:
|
||||
new_unified_job.status = 'failed'
|
||||
new_unified_job.job_explanation = gettext_noop("Scheduled job could not start because it \
|
||||
was not in the right state or required manual credentials")
|
||||
new_unified_job.job_explanation = gettext_noop(
|
||||
"Scheduled job could not start because it \
|
||||
was not in the right state or required manual credentials"
|
||||
)
|
||||
new_unified_job.save(update_fields=['status', 'job_explanation'])
|
||||
new_unified_job.websocket_emit_status("failed")
|
||||
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
|
||||
|
||||
@@ -4,9 +4,7 @@ from django import template
|
||||
register = template.Library()
|
||||
|
||||
CONSONANT_SOUND = re.compile(r'''one(![ir])''', re.IGNORECASE | re.VERBOSE) # noqa
|
||||
VOWEL_SOUND = re.compile(
|
||||
r'''[aeio]|u([aeiou]|[^n][^aeiou]|ni[^dmnl]|nil[^l])|h(ier|onest|onou?r|ors\b|our(!i))|[fhlmnrsx]\b''', re.IGNORECASE | re.VERBOSE
|
||||
) # noqa
|
||||
VOWEL_SOUND = re.compile(r'''[aeio]|u([aeiou]|[^n][^aeiou]|ni[^dmnl]|nil[^l])|h(ier|onest|onou?r|ors\b|our(!i))|[fhlmnrsx]\b''', re.IGNORECASE | re.VERBOSE) # noqa
|
||||
|
||||
|
||||
@register.filter
|
||||
|
||||
@@ -166,6 +166,6 @@ def test_copy_tables_workflow_job_node_query(sqlite_copy, workflow_job):
|
||||
for index, relationship in zip([7, 8, 9], ["success_nodes", "failure_nodes", "always_nodes"]):
|
||||
for i, l in enumerate(lines):
|
||||
related_nodes = [int(e) for e in l[index].split(",")] if l[index] else []
|
||||
assert related_nodes == list(
|
||||
getattr(workflow_job.workflow_nodes.all()[i], relationship).all().values_list("id", flat=True)
|
||||
), f"(right side) workflow_nodes.all()[{i}].{relationship}.all()"
|
||||
assert related_nodes == list(getattr(workflow_job.workflow_nodes.all()[i], relationship).all().values_list("id", flat=True)), (
|
||||
f"(right side) workflow_nodes.all()[{i}].{relationship}.all()"
|
||||
)
|
||||
|
||||
@@ -233,9 +233,10 @@ class TestAnalyticsGenericView:
|
||||
request = RequestFactory().post('/some/path')
|
||||
view = AnalyticsGenericView()
|
||||
|
||||
with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client, mock.patch(
|
||||
'awx.api.views.analytics.AnalyticsGenericView._base_auth_request'
|
||||
) as mock_base_auth_request:
|
||||
with (
|
||||
mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client,
|
||||
mock.patch('awx.api.views.analytics.AnalyticsGenericView._base_auth_request') as mock_base_auth_request,
|
||||
):
|
||||
# Configure the mock OIDCClient instance and its make_request method
|
||||
mock_client_instance = mock.Mock()
|
||||
mock_oidc_client.return_value = mock_client_instance
|
||||
|
||||
@@ -930,7 +930,7 @@ def test_field_removal(put, organization, admin, credentialtype_ssh):
|
||||
},
|
||||
}
|
||||
cred = Credential(
|
||||
credential_type=credentialtype_ssh, name='Best credential ever', organization=organization, inputs={'username': u'jim', 'password': u'secret'}
|
||||
credential_type=credentialtype_ssh, name='Best credential ever', organization=organization, inputs={'username': 'jim', 'password': 'secret'}
|
||||
)
|
||||
cred.save()
|
||||
|
||||
@@ -957,7 +957,7 @@ def test_field_removal(put, organization, admin, credentialtype_ssh):
|
||||
)
|
||||
def test_credential_type_mutability(patch, organization, admin, credentialtype_ssh, credentialtype_aws, relation, related_obj):
|
||||
cred = Credential(
|
||||
credential_type=credentialtype_ssh, name='Best credential ever', organization=organization, inputs={'username': u'jim', 'password': u'pass'}
|
||||
credential_type=credentialtype_ssh, name='Best credential ever', organization=organization, inputs={'username': 'jim', 'password': 'pass'}
|
||||
)
|
||||
cred.save()
|
||||
|
||||
@@ -967,7 +967,7 @@ def test_credential_type_mutability(patch, organization, admin, credentialtype_s
|
||||
def _change_credential_type():
|
||||
return patch(
|
||||
reverse('api:credential_detail', kwargs={'pk': cred.pk}),
|
||||
{'credential_type': credentialtype_aws.pk, 'inputs': {'username': u'jim', 'password': u'pass'}},
|
||||
{'credential_type': credentialtype_aws.pk, 'inputs': {'username': 'jim', 'password': 'pass'}},
|
||||
admin,
|
||||
)
|
||||
|
||||
@@ -992,7 +992,7 @@ def test_vault_credential_type_mutability(patch, organization, admin, credential
|
||||
name='Best credential ever',
|
||||
organization=organization,
|
||||
inputs={
|
||||
'vault_password': u'some-vault',
|
||||
'vault_password': 'some-vault',
|
||||
},
|
||||
)
|
||||
cred.save()
|
||||
@@ -1004,7 +1004,7 @@ def test_vault_credential_type_mutability(patch, organization, admin, credential
|
||||
def _change_credential_type():
|
||||
return patch(
|
||||
reverse('api:credential_detail', kwargs={'pk': cred.pk}),
|
||||
{'credential_type': credentialtype_ssh.pk, 'inputs': {'username': u'jim', 'password': u'pass'}},
|
||||
{'credential_type': credentialtype_ssh.pk, 'inputs': {'username': 'jim', 'password': 'pass'}},
|
||||
admin,
|
||||
)
|
||||
|
||||
@@ -1025,7 +1025,7 @@ def test_vault_credential_type_mutability(patch, organization, admin, credential
|
||||
@pytest.mark.django_db
|
||||
def test_cloud_credential_type_mutability(patch, organization, admin, credentialtype_ssh, credentialtype_aws):
|
||||
cred = Credential(
|
||||
credential_type=credentialtype_aws, name='Best credential ever', organization=organization, inputs={'username': u'jim', 'password': u'pass'}
|
||||
credential_type=credentialtype_aws, name='Best credential ever', organization=organization, inputs={'username': 'jim', 'password': 'pass'}
|
||||
)
|
||||
cred.save()
|
||||
|
||||
@@ -1036,7 +1036,7 @@ def test_cloud_credential_type_mutability(patch, organization, admin, credential
|
||||
def _change_credential_type():
|
||||
return patch(
|
||||
reverse('api:credential_detail', kwargs={'pk': cred.pk}),
|
||||
{'credential_type': credentialtype_ssh.pk, 'inputs': {'username': u'jim', 'password': u'pass'}},
|
||||
{'credential_type': credentialtype_ssh.pk, 'inputs': {'username': 'jim', 'password': 'pass'}},
|
||||
admin,
|
||||
)
|
||||
|
||||
@@ -1084,7 +1084,7 @@ def test_ssh_unlock_needed(put, organization, admin, credentialtype_ssh):
|
||||
credential_type=credentialtype_ssh,
|
||||
name='Best credential ever',
|
||||
organization=organization,
|
||||
inputs={'username': u'joe', 'ssh_key_data': EXAMPLE_ENCRYPTED_PRIVATE_KEY, 'ssh_key_unlock': 'unlock'},
|
||||
inputs={'username': 'joe', 'ssh_key_data': EXAMPLE_ENCRYPTED_PRIVATE_KEY, 'ssh_key_unlock': 'unlock'},
|
||||
)
|
||||
cred.save()
|
||||
|
||||
@@ -1110,7 +1110,7 @@ def test_ssh_unlock_not_needed(put, organization, admin, credentialtype_ssh):
|
||||
name='Best credential ever',
|
||||
organization=organization,
|
||||
inputs={
|
||||
'username': u'joe',
|
||||
'username': 'joe',
|
||||
'ssh_key_data': EXAMPLE_PRIVATE_KEY,
|
||||
},
|
||||
)
|
||||
@@ -1137,7 +1137,7 @@ def test_ssh_unlock_with_prior_value(put, organization, admin, credentialtype_ss
|
||||
credential_type=credentialtype_ssh,
|
||||
name='Best credential ever',
|
||||
organization=organization,
|
||||
inputs={'username': u'joe', 'ssh_key_data': EXAMPLE_ENCRYPTED_PRIVATE_KEY, 'ssh_key_unlock': 'old-unlock'},
|
||||
inputs={'username': 'joe', 'ssh_key_data': EXAMPLE_ENCRYPTED_PRIVATE_KEY, 'ssh_key_unlock': 'old-unlock'},
|
||||
)
|
||||
cred.save()
|
||||
|
||||
@@ -1165,7 +1165,7 @@ def test_ssh_bad_key_unlock_not_checked(put, organization, admin, credentialtype
|
||||
name='Best credential ever',
|
||||
organization=organization,
|
||||
inputs={
|
||||
'username': u'oscar',
|
||||
'username': 'oscar',
|
||||
'ssh_key_data': 'invalid-key',
|
||||
'ssh_key_unlock': 'unchecked-unlock',
|
||||
},
|
||||
@@ -1251,7 +1251,7 @@ def test_secret_encryption_previous_value(patch, organization, admin, credential
|
||||
}
|
||||
}
|
||||
cred = Credential(
|
||||
credential_type=credentialtype_ssh, name='Best credential ever', organization=organization, inputs={'username': u'jim', 'password': u'secret'}
|
||||
credential_type=credentialtype_ssh, name='Best credential ever', organization=organization, inputs={'username': 'jim', 'password': 'secret'}
|
||||
)
|
||||
cred.save()
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ def test_delete_instance_group_jobs_running(delete, instance_group_jobs_running,
|
||||
response_sorted = sorted(response.data['active_jobs'], key=sort_keys)
|
||||
expect_sorted = sorted(expect_transformed, key=sort_keys)
|
||||
|
||||
assert response.data['error'] == u"Resource is being used by running jobs."
|
||||
assert response.data['error'] == "Resource is being used by running jobs."
|
||||
assert response_sorted == expect_sorted
|
||||
|
||||
|
||||
|
||||
@@ -285,12 +285,12 @@ def test_urlencode_host_filter(post, admin_user, organization):
|
||||
def test_host_filter_unicode(post, admin_user, organization):
|
||||
post(
|
||||
reverse('api:inventory_list'),
|
||||
data={'name': 'smart inventory', 'kind': 'smart', 'organization': organization.pk, 'host_filter': u'ansible_facts__ansible_distribution=レッドハット'},
|
||||
data={'name': 'smart inventory', 'kind': 'smart', 'organization': organization.pk, 'host_filter': 'ansible_facts__ansible_distribution=レッドハット'},
|
||||
user=admin_user,
|
||||
expect=201,
|
||||
)
|
||||
si = Inventory.objects.get(name='smart inventory')
|
||||
assert si.host_filter == u'ansible_facts__ansible_distribution=レッドハット'
|
||||
assert si.host_filter == 'ansible_facts__ansible_distribution=レッドハット'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -302,7 +302,7 @@ def test_host_filter_invalid_ansible_facts_lookup(post, admin_user, organization
|
||||
'name': 'smart inventory',
|
||||
'kind': 'smart',
|
||||
'organization': organization.pk,
|
||||
'host_filter': u'ansible_facts__ansible_distribution__{}=cent'.format(lookup),
|
||||
'host_filter': 'ansible_facts__ansible_distribution__{}=cent'.format(lookup),
|
||||
},
|
||||
user=admin_user,
|
||||
expect=400,
|
||||
@@ -417,7 +417,7 @@ def test_inventory_source_vars_prohibition(post, inventory, admin_user):
|
||||
mock_settings.INV_ENV_VARIABLE_BLOCKED = ('FOOBAR',)
|
||||
r = post(
|
||||
reverse('api:inventory_source_list'),
|
||||
{'name': 'new inv src', 'source_vars': '{\"FOOBAR\": \"val\"}', 'inventory': inventory.pk},
|
||||
{'name': 'new inv src', 'source_vars': '{"FOOBAR": "val"}', 'inventory': inventory.pk},
|
||||
admin_user,
|
||||
expect=400,
|
||||
)
|
||||
@@ -729,7 +729,6 @@ class TestConstructedInventory:
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestInventoryAllVariables:
|
||||
|
||||
@staticmethod
|
||||
def simulate_update_from_source(inv_src, variables_dict, overwrite_vars=True):
|
||||
"""
|
||||
|
||||
@@ -278,9 +278,9 @@ def test_job_reject_invalid_prompted_vars(runtime_data, job_template_prompts, po
|
||||
expect=400,
|
||||
)
|
||||
|
||||
assert response.data['job_type'] == [u'"foobicate" is not a valid choice.']
|
||||
assert response.data['inventory'] == [u'Invalid pk "87865" - object does not exist.']
|
||||
assert response.data['credentials'] == [u'Invalid pk "48474" - object does not exist.']
|
||||
assert response.data['job_type'] == ['"foobicate" is not a valid choice.']
|
||||
assert response.data['inventory'] == ['Invalid pk "87865" - object does not exist.']
|
||||
assert response.data['credentials'] == ['Invalid pk "48474" - object does not exist.']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -314,7 +314,7 @@ def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime
|
||||
# Assure that giving an inventory without access to the inventory blocks the launch
|
||||
response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), dict(inventory=runtime_data['inventory']), rando, expect=403)
|
||||
|
||||
assert response.data['detail'] == u'You do not have permission to perform this action.'
|
||||
assert response.data['detail'] == 'You do not have permission to perform this action.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -540,15 +540,15 @@ def test_job_launch_fails_with_missing_multivault_password(machine_credential, v
|
||||
assert {
|
||||
'credential_type': vault_cred_first.credential_type_id,
|
||||
'passwords_needed': ['vault_password.abc'],
|
||||
'vault_id': u'abc',
|
||||
'name': u'Vault #1',
|
||||
'vault_id': 'abc',
|
||||
'name': 'Vault #1',
|
||||
'id': vault_cred_first.id,
|
||||
} in resp.data['defaults']['credentials']
|
||||
assert {
|
||||
'credential_type': vault_cred_second.credential_type_id,
|
||||
'passwords_needed': ['vault_password.xyz'],
|
||||
'vault_id': u'xyz',
|
||||
'name': u'Vault #2',
|
||||
'vault_id': 'xyz',
|
||||
'name': 'Vault #2',
|
||||
'id': vault_cred_second.id,
|
||||
} in resp.data['defaults']['credentials']
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ class TestLicenseCacheClearing:
|
||||
"""Test that posting a manifest to /api/v2/config/ clears the LICENSE cache"""
|
||||
|
||||
# Mock the licenser and clear_setting_cache
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
|
||||
'awx.api.views.root.clear_setting_cache'
|
||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
with (
|
||||
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||
patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate,
|
||||
patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache,
|
||||
patch('django.db.connection.on_commit') as mock_on_commit,
|
||||
):
|
||||
# Set up mock license data
|
||||
mock_license_data = {'valid_key': True, 'license_type': 'enterprise', 'instance_count': 100, 'subscription_name': 'Test Enterprise License'}
|
||||
|
||||
@@ -54,7 +56,6 @@ class TestLicenseCacheClearing:
|
||||
"""Test that DELETE /api/v2/config/ clears the LICENSE cache"""
|
||||
|
||||
with patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
# Make the DELETE request
|
||||
url = reverse('api:api_v2_config_view')
|
||||
delete(url, admin_user, expect=204)
|
||||
@@ -72,10 +73,12 @@ class TestLicenseCacheClearing:
|
||||
def test_attach_view_clears_cache(self, admin_user, post):
|
||||
"""Test that posting to /api/v2/config/attach/ clears the LICENSE cache"""
|
||||
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch(
|
||||
'django.db.connection.on_commit'
|
||||
) as mock_on_commit, patch('awx.api.views.root.settings') as mock_settings:
|
||||
|
||||
with (
|
||||
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||
patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache,
|
||||
patch('django.db.connection.on_commit') as mock_on_commit,
|
||||
patch('awx.api.views.root.settings') as mock_settings,
|
||||
):
|
||||
# Set up subscription credentials in settings
|
||||
mock_settings.SUBSCRIPTIONS_CLIENT_ID = 'test-client-id'
|
||||
mock_settings.SUBSCRIPTIONS_CLIENT_SECRET = 'test-client-secret'
|
||||
@@ -113,10 +116,11 @@ class TestLicenseCacheClearing:
|
||||
def test_attach_view_subscription_not_found_no_cache_clear(self, admin_user, post):
|
||||
"""Test that attach view doesn't clear cache when subscription is not found"""
|
||||
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch(
|
||||
'django.db.connection.on_commit'
|
||||
) as mock_on_commit:
|
||||
|
||||
with (
|
||||
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||
patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache,
|
||||
patch('django.db.connection.on_commit') as mock_on_commit,
|
||||
):
|
||||
# Set up mock licenser with different subscription
|
||||
mock_licenser = MagicMock()
|
||||
subscription_data = {'subscription_id': 'different-subscription-456', 'valid_key': False, 'license_type': 'enterprise'} # Different ID
|
||||
@@ -142,10 +146,11 @@ class TestLicenseCacheClearing:
|
||||
def test_manifest_validation_error_no_cache_clear(self, admin_user, post):
|
||||
"""Test that config view doesn't clear cache when manifest validation fails"""
|
||||
|
||||
with patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
|
||||
'awx.api.views.root.clear_setting_cache'
|
||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
with (
|
||||
patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate,
|
||||
patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache,
|
||||
patch('django.db.connection.on_commit') as mock_on_commit,
|
||||
):
|
||||
# Mock validation to raise ValueError
|
||||
mock_validate.side_effect = ValueError("Invalid manifest")
|
||||
|
||||
@@ -166,10 +171,12 @@ class TestLicenseCacheClearing:
|
||||
def test_license_processing_error_no_cache_clear(self, admin_user, post):
|
||||
"""Test that config view doesn't clear cache when license processing fails"""
|
||||
|
||||
with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
|
||||
'awx.api.views.root.clear_setting_cache'
|
||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
||||
|
||||
with (
|
||||
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||
patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate,
|
||||
patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache,
|
||||
patch('django.db.connection.on_commit') as mock_on_commit,
|
||||
):
|
||||
# Mock validation to succeed but license processing to fail
|
||||
mock_validate.return_value = [{'some': 'manifest_data'}]
|
||||
mock_licenser = MagicMock()
|
||||
|
||||
@@ -62,10 +62,11 @@ def oidc_credential(oidc_credentialtype):
|
||||
@pytest.fixture
|
||||
def mock_oidc_backend():
|
||||
"""Fixture that mocks OIDC JWT generation and credential backend."""
|
||||
with mock.patch('awx.api.views.retrieve_workload_identity_jwt_with_claims') as mock_jwt, mock.patch('awx.api.views._jwt_decode') as mock_decode, mock.patch(
|
||||
'awx.main.models.credential.CredentialType.plugin', new_callable=mock.PropertyMock
|
||||
) as mock_plugin:
|
||||
|
||||
with (
|
||||
mock.patch('awx.api.views.retrieve_workload_identity_jwt_with_claims') as mock_jwt,
|
||||
mock.patch('awx.api.views._jwt_decode') as mock_decode,
|
||||
mock.patch('awx.main.models.credential.CredentialType.plugin', new_callable=mock.PropertyMock) as mock_plugin,
|
||||
):
|
||||
# Set default return values
|
||||
mock_jwt.return_value = 'fake.jwt.token'
|
||||
mock_decode.return_value = {'iss': 'http://gateway/o', 'aud': 'vault'}
|
||||
|
||||
@@ -272,7 +272,7 @@ def test_organization_delete_with_active_jobs(delete, admin, organization, organ
|
||||
resp_sorted = sorted(resp.data['active_jobs'], key=sort_keys)
|
||||
expect_sorted = sorted(expect_transformed, key=sort_keys)
|
||||
|
||||
assert resp.data['error'] == u"Resource is being used by running jobs."
|
||||
assert resp.data['error'] == "Resource is being used by running jobs."
|
||||
assert resp_sorted == expect_sorted
|
||||
|
||||
|
||||
|
||||
@@ -150,13 +150,13 @@ def test_host_filter_unicode(post, admin_user, smart_inv_org):
|
||||
'name': 'unicode-smart',
|
||||
'kind': 'smart',
|
||||
'organization': smart_inv_org.pk,
|
||||
'host_filter': u'ansible_facts__ansible_distribution=レッドハット',
|
||||
'host_filter': 'ansible_facts__ansible_distribution=レッドハット',
|
||||
},
|
||||
user=admin_user,
|
||||
expect=201,
|
||||
)
|
||||
si = Inventory.objects.get(name='unicode-smart')
|
||||
assert si.host_filter == u'ansible_facts__ansible_distribution=レッドハット'
|
||||
assert si.host_filter == 'ansible_facts__ansible_distribution=レッドハット'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -168,7 +168,7 @@ def test_host_filter_invalid_ansible_facts_lookup(post, admin_user, smart_inv_or
|
||||
'name': 'invalid-lookup-smart',
|
||||
'kind': 'smart',
|
||||
'organization': smart_inv_org.pk,
|
||||
'host_filter': u'ansible_facts__ansible_distribution__{}=cent'.format(lookup),
|
||||
'host_filter': 'ansible_facts__ansible_distribution__{}=cent'.format(lookup),
|
||||
},
|
||||
user=admin_user,
|
||||
expect=400,
|
||||
|
||||
@@ -168,8 +168,9 @@ def test_text_stdout_with_max_stdout(sqlite_copy, get, admin):
|
||||
url = reverse('api:system_job_detail', kwargs={'pk': job.pk})
|
||||
response = get(url, user=admin, expect=200)
|
||||
assert response.data['result_stdout'] == (
|
||||
'Standard Output too large to display ({actual} bytes), only download '
|
||||
'supported for sizes over {max} bytes.'.format(actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
'Standard Output too large to display ({actual} bytes), only download supported for sizes over {max} bytes.'.format(
|
||||
actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -196,8 +197,9 @@ def test_max_bytes_display(sqlite_copy, Parent, Child, relation, view, fmt, get,
|
||||
|
||||
response = get(url + '?format={}'.format(fmt), user=admin, expect=200)
|
||||
assert smart_str(response.content) == (
|
||||
'Standard Output too large to display ({actual} bytes), only download '
|
||||
'supported for sizes over {max} bytes.'.format(actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
'Standard Output too large to display ({actual} bytes), only download supported for sizes over {max} bytes.'.format(
|
||||
actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY
|
||||
)
|
||||
)
|
||||
|
||||
response = get(url + '?format={}_download'.format(fmt), user=admin, expect=200)
|
||||
@@ -236,8 +238,9 @@ def test_legacy_result_stdout_with_max_bytes(Cls, view, fmt, get, admin):
|
||||
|
||||
response = get(url + '?format={}'.format(fmt), user=admin, expect=200)
|
||||
assert smart_str(response.content) == (
|
||||
'Standard Output too large to display ({actual} bytes), only download '
|
||||
'supported for sizes over {max} bytes.'.format(actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||
'Standard Output too large to display ({actual} bytes), only download supported for sizes over {max} bytes.'.format(
|
||||
actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY
|
||||
)
|
||||
)
|
||||
|
||||
response = get(url + '?format={}'.format(fmt + '_download'), user=admin, expect=200)
|
||||
@@ -259,7 +262,7 @@ def test_text_with_unicode_stdout(sqlite_copy, Parent, Child, relation, view, ge
|
||||
job = Parent()
|
||||
job.save()
|
||||
for i in range(3):
|
||||
Child(**{relation: job, 'stdout': u'オ{}\n'.format(i), 'start_line': i}).save()
|
||||
Child(**{relation: job, 'stdout': 'オ{}\n'.format(i), 'start_line': i}).save()
|
||||
url = reverse(view, kwargs={'pk': job.pk}) + '?format=' + fmt
|
||||
|
||||
response = get(url, user=admin, expect=200)
|
||||
|
||||
@@ -126,7 +126,7 @@ class TestKeyRegeneration:
|
||||
name='Example Template',
|
||||
survey_spec=survey_spec_factory([{'variable': 'secret_key', 'default': encrypt_value('donttell', pk=None), 'type': 'password'}]),
|
||||
survey_enabled=True,
|
||||
**params
|
||||
**params,
|
||||
)
|
||||
job = jt.create_unified_job()
|
||||
assert jt.survey_spec['spec'][0]['default'].startswith(PREFIX)
|
||||
|
||||
@@ -15,7 +15,6 @@ from awx.main.models import Organization, Team
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestClaimsOldRbacSync:
|
||||
|
||||
def _build_claims(self, orgs, teams):
|
||||
"""Build a claims dict from org/team model instances."""
|
||||
objects = {"organization": [], "team": []}
|
||||
|
||||
@@ -85,12 +85,12 @@ def test_consolidate_indirect_user_roles_with_nested_teams(setup_managed_roles,
|
||||
assert team_g_users_after == set(team_g.member_role.members.all().values_list('id', flat=True))
|
||||
|
||||
# Verify team-to-team relationships are removed after consolidation
|
||||
assert not RoleTeamAssignment.objects.filter(
|
||||
role_definition=team_member_role, team=team_f, object_id=team_e.id
|
||||
).exists(), "Team-to-team relationship F→E should be removed"
|
||||
assert not RoleTeamAssignment.objects.filter(
|
||||
role_definition=team_member_role, team=team_g, object_id=team_f.id
|
||||
).exists(), "Team-to-team relationship G→F should be removed"
|
||||
assert not RoleTeamAssignment.objects.filter(role_definition=team_member_role, team=team_f, object_id=team_e.id).exists(), (
|
||||
"Team-to-team relationship F→E should be removed"
|
||||
)
|
||||
assert not RoleTeamAssignment.objects.filter(role_definition=team_member_role, team=team_g, object_id=team_f.id).exists(), (
|
||||
"Team-to-team relationship G→F should be removed"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestHostManagerDeferral:
|
||||
"""ansible_facts should be in the deferred set for default Host queries."""
|
||||
qs = Host.objects.all()
|
||||
deferred = qs.query.deferred_loading[0]
|
||||
assert 'ansible_facts' in deferred, f'ansible_facts should be deferred by the HostManager. ' f'Deferred fields: {deferred}'
|
||||
assert 'ansible_facts' in deferred, f'ansible_facts should be deferred by the HostManager. Deferred fields: {deferred}'
|
||||
|
||||
def test_ansible_facts_accessible_when_needed(self, inventory):
|
||||
"""Deferred fields are still accessible — Django fetches on access."""
|
||||
|
||||
@@ -62,9 +62,9 @@ def test_role_migration_matches(request, model, setup_managed_roles):
|
||||
rd_data = {}
|
||||
for rd in model_rds:
|
||||
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
||||
assert (
|
||||
'Compat' not in rd.name
|
||||
), f'Permissions for old vs new roles did not match.\nold {field.name}: {old_codenames}\nnew:\n{json.dumps(rd_data, indent=2)}'
|
||||
assert 'Compat' not in rd.name, (
|
||||
f'Permissions for old vs new roles did not match.\nold {field.name}: {old_codenames}\nnew:\n{json.dumps(rd_data, indent=2)}'
|
||||
)
|
||||
assert new_codenames == set(old_codenames)
|
||||
|
||||
# In the old system these models did not have object-level roles, all others expect some model roles
|
||||
|
||||
@@ -135,7 +135,7 @@ def test_unified_job_list_team_member_sees_team_granted_jobs(user, setup_managed
|
||||
response = get(reverse('api:unified_job_list'), team_user)
|
||||
assert response.status_code == 200
|
||||
result_ids = [r['id'] for r in response.data['results']]
|
||||
assert job.pk in result_ids, f"Team member should see job {job.pk} via team-granted JT execute permission, " f"but got result IDs: {result_ids}"
|
||||
assert job.pk in result_ids, f"Team member should see job {job.pk} via team-granted JT execute permission, but got result IDs: {result_ids}"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -171,7 +171,7 @@ class TestFinishFactCacheConcurrentProtection:
|
||||
)
|
||||
|
||||
host.refresh_from_db()
|
||||
assert host.ansible_facts == {}, 'Stale facts should have been cleared when the fact file is missing ' 'and ansible_facts_modified predates job_created'
|
||||
assert host.ansible_facts == {}, 'Stale facts should have been cleared when the fact file is missing and ansible_facts_modified predates job_created'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -230,9 +230,9 @@ def test_inventory_update_injected_content(product_name, this_kind, inventory, f
|
||||
|
||||
# Assert inventory plugin inventory file is in private_data_dir
|
||||
inventory_filename = InventorySource.injectors[inventory_update.source]().filename
|
||||
assert (
|
||||
len([True for k in content.keys() if k.endswith(inventory_filename)]) > 0
|
||||
), f"'{inventory_filename}' file not found in inventory update runtime files {content.keys()}"
|
||||
assert len([True for k in content.keys() if k.endswith(inventory_filename)]) > 0, (
|
||||
f"'{inventory_filename}' file not found in inventory update runtime files {content.keys()}"
|
||||
)
|
||||
|
||||
env.pop('ANSIBLE_COLLECTIONS_PATH', None)
|
||||
base_dir = os.path.join(DATA, 'plugins')
|
||||
|
||||
@@ -164,15 +164,15 @@ class TestMigrationSmoke:
|
||||
|
||||
CredentialType = new_state.apps.get_model('main', 'CredentialType')
|
||||
for expected_name in validate_exists:
|
||||
assert CredentialType.objects.filter(
|
||||
name=expected_name
|
||||
).exists(), f'Could not find {expected_name} credential type name, all names: {list(CredentialType.objects.values_list("name", flat=True))}'
|
||||
assert CredentialType.objects.filter(name=expected_name).exists(), (
|
||||
f'Could not find {expected_name} credential type name, all names: {list(CredentialType.objects.values_list("name", flat=True))}'
|
||||
)
|
||||
|
||||
# Verify the system_administrator role exists
|
||||
Role = new_state.apps.get_model('main', 'Role')
|
||||
assert Role.objects.filter(
|
||||
singleton_name='system_administrator', role_field='system_administrator'
|
||||
).exists(), "expected to find a system_administrator singleton role"
|
||||
assert Role.objects.filter(singleton_name='system_administrator', role_field='system_administrator').exists(), (
|
||||
"expected to find a system_administrator singleton role"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -215,15 +215,15 @@ class TestGithubAppBug:
|
||||
CredentialType = new_state.apps.get_model('main', 'CredentialType') # Get CredentialType model from the new state
|
||||
|
||||
# Assertion 1: The CredentialType with the old 'github_app' kind should no longer exist.
|
||||
assert not CredentialType.objects.filter(
|
||||
namespace='github_app'
|
||||
).exists(), "CredentialType with old 'github_app' kind should no longer exist after migration."
|
||||
assert not CredentialType.objects.filter(namespace='github_app').exists(), (
|
||||
"CredentialType with old 'github_app' kind should no longer exist after migration."
|
||||
)
|
||||
|
||||
# Assertion 2: The CredentialType should now exist with the new 'github_app_lookup' kind
|
||||
# and retain its original name.
|
||||
assert CredentialType.objects.filter(
|
||||
namespace='github_app_lookup', name='Legacy GitHub App Credential'
|
||||
).exists(), "CredentialType should be updated to 'github_app_lookup' and retain its name."
|
||||
assert CredentialType.objects.filter(namespace='github_app_lookup', name='Legacy GitHub App Credential').exists(), (
|
||||
"CredentialType should be updated to 'github_app_lookup' and retain its name."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -245,9 +245,9 @@ class TestSystemAuditorMigration:
|
||||
new_state = migrator.apply_tested_migration(('main', '0192_custom_roles'))
|
||||
RoleUserAssignment = new_state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||
|
||||
assert RoleUserAssignment.objects.filter(
|
||||
user=user.id, role_definition__name='Platform Auditor'
|
||||
).exists(), "system_auditor member should have been granted Platform Auditor"
|
||||
assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').exists(), (
|
||||
"system_auditor member should have been granted Platform Auditor"
|
||||
)
|
||||
|
||||
def test_fix_system_auditor_migration_backfills_missing(self, migrator):
|
||||
"""
|
||||
@@ -269,16 +269,16 @@ class TestSystemAuditorMigration:
|
||||
# Simulate the pre-fix state: remove the Platform Auditor assignment
|
||||
platform_auditor = RoleDefinition.objects.get(name='Platform Auditor')
|
||||
RoleUserAssignment.objects.filter(user=user.id, role_definition=platform_auditor).delete()
|
||||
assert not RoleUserAssignment.objects.filter(
|
||||
user=user.id, role_definition=platform_auditor
|
||||
).exists(), "Setup: user should not have Platform Auditor before corrective migration"
|
||||
assert not RoleUserAssignment.objects.filter(user=user.id, role_definition=platform_auditor).exists(), (
|
||||
"Setup: user should not have Platform Auditor before corrective migration"
|
||||
)
|
||||
|
||||
state = migrator.apply_tested_migration(('main', '0208_fix_system_auditor_migration'))
|
||||
RoleUserAssignment = state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||
|
||||
assert RoleUserAssignment.objects.filter(
|
||||
user=user.id, role_definition__name='Platform Auditor'
|
||||
).exists(), "Corrective migration should have backfilled Platform Auditor"
|
||||
assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').exists(), (
|
||||
"Corrective migration should have backfilled Platform Auditor"
|
||||
)
|
||||
|
||||
def test_fix_system_auditor_migration_no_duplicates(self, migrator):
|
||||
"""
|
||||
@@ -302,6 +302,6 @@ class TestSystemAuditorMigration:
|
||||
state = migrator.apply_tested_migration(('main', '0208_fix_system_auditor_migration'))
|
||||
RoleUserAssignment = state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||
|
||||
assert (
|
||||
RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').count() == 1
|
||||
), "Should have exactly one Platform Auditor assignment, not duplicates"
|
||||
assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').count() == 1, (
|
||||
"Should have exactly one Platform Auditor assignment, not duplicates"
|
||||
)
|
||||
|
||||
@@ -163,9 +163,11 @@ def test_custom_environment_injection(post, user, organization):
|
||||
expect=201,
|
||||
)
|
||||
template = NotificationTemplate.objects.get(pk=response.data['id'])
|
||||
with pytest.raises(ConnectionError), override_settings(AWX_TASK_ENV={'HTTPS_PROXY': '192.168.50.100:1234'}), mock.patch.object(
|
||||
HTTPAdapter, 'send'
|
||||
) as fake_send:
|
||||
with (
|
||||
pytest.raises(ConnectionError),
|
||||
override_settings(AWX_TASK_ENV={'HTTPS_PROXY': '192.168.50.100:1234'}),
|
||||
mock.patch.object(HTTPAdapter, 'send') as fake_send,
|
||||
):
|
||||
|
||||
def _send_side_effect(request, **kw):
|
||||
assert select_proxy(request.url, kw['proxies']) == '192.168.50.100:1234'
|
||||
|
||||
@@ -218,7 +218,7 @@ def test_embedded_query_takes_precedence(live_tmp_folder, run_job_from_playbook,
|
||||
# Verify the embedded query was used (includes device_type in facts)
|
||||
host_audit = IndirectManagedNodeAudit.objects.filter(job=job).first()
|
||||
assert host_audit.facts == {'device_type': 'Fake Host'}, (
|
||||
'Expected embedded query output (with device_type). ' 'If facts is {}, the external query was incorrectly used instead.'
|
||||
'Expected embedded query output (with device_type). If facts is {}, the external query was incorrectly used instead.'
|
||||
)
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ def test_fallback_does_not_overcount(live_tmp_folder, run_job_from_playbook, ena
|
||||
|
||||
# Critical safety check: fallback must never count MORE than exact
|
||||
assert fallback_count <= exact_count, (
|
||||
f'Overcounting detected! Fallback produced {fallback_count} records ' f'but exact match produced only {exact_count} records.'
|
||||
f'Overcounting detected! Fallback produced {fallback_count} records but exact match produced only {exact_count} records.'
|
||||
)
|
||||
|
||||
# Both use the same jq expression and same module, so counts should be equal
|
||||
@@ -343,5 +343,5 @@ def test_no_counting_without_compatible_fallback(live_tmp_folder, run_job_from_p
|
||||
|
||||
# No audit records should exist for this job
|
||||
assert IndirectManagedNodeAudit.objects.filter(job=job).count() == 0, (
|
||||
'IndirectManagedNodeAudit records were created despite no compatible ' 'fallback existing for demo.external v3.0.0 (only v1.x queries available).'
|
||||
'IndirectManagedNodeAudit records were created despite no compatible fallback existing for demo.external v3.0.0 (only v1.x queries available).'
|
||||
)
|
||||
|
||||
@@ -120,7 +120,7 @@ def test_nested_workflow_set_stats_precedence(live_tmp_folder, demo_inv, project
|
||||
reader_extra_vars = json.loads(reader_node.job.extra_vars)
|
||||
|
||||
# var1: only set by outer job_first, no conflict — should propagate through
|
||||
assert reader_extra_vars.get('var1') == 'outer-only', f'var1: expected "outer-only" (uncontested outer artifact), ' f'got "{reader_extra_vars.get("var1")}"'
|
||||
assert reader_extra_vars.get('var1') == 'outer-only', f'var1: expected "outer-only" (uncontested outer artifact), got "{reader_extra_vars.get("var1")}"'
|
||||
|
||||
# var2: set by outer as "should-be-overridden", then by inner as "from-inner"
|
||||
# Inner workflow's own ancestor artifacts should take precedence
|
||||
@@ -132,7 +132,7 @@ def test_nested_workflow_set_stats_precedence(live_tmp_folder, demo_inv, project
|
||||
)
|
||||
|
||||
# var3: only set by inner job_second — should propagate normally
|
||||
assert reader_extra_vars.get('var3') == 'inner-only', f'var3: expected "inner-only" (inner-only artifact), ' f'got "{reader_extra_vars.get("var3")}"'
|
||||
assert reader_extra_vars.get('var3') == 'inner-only', f'var3: expected "inner-only" (inner-only artifact), got "{reader_extra_vars.get("var3")}"'
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
|
||||
@@ -76,7 +76,7 @@ class TestJobSerializerGetRelated:
|
||||
class TestJobSerializerSubstitution:
|
||||
def test_survey_password_hide(self, mocker):
|
||||
job = mocker.MagicMock(
|
||||
**{'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}', 'extra_vars.return_value': '{\"secret_key\": \"my_password\"}'}
|
||||
**{'display_extra_vars.return_value': '{"secret_key": "$encrypted$"}', 'extra_vars.return_value': '{"secret_key": "my_password"}'}
|
||||
)
|
||||
serializer = JobSerializer(job)
|
||||
rep = serializer.to_representation(job)
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestNotificationTemplateSerializer:
|
||||
},
|
||||
{'started': {'body': '{{ job_metadata }}'}},
|
||||
{'started': {'body': '{{ job.summary_fields.inventory.total_hosts }}'}},
|
||||
{'started': {'body': u'Iñtërnâtiônàlizætiøn'}},
|
||||
{'started': {'body': 'Iñtërnâtiônàlizætiøn'}},
|
||||
],
|
||||
)
|
||||
def test_valid_messages(self, valid_messages):
|
||||
|
||||
@@ -29,9 +29,9 @@ def test_unified_job_list_field_consistency():
|
||||
for cls in UnifiedJob.__subclasses__():
|
||||
list_serializer = getattr(serializers, '{}ListSerializer'.format(cls.__name__))
|
||||
unified_serializer = serializers.UnifiedJobListSerializer().get_sub_serializer(cls())
|
||||
assert set(list_serializer().fields.keys()) == set(
|
||||
unified_serializer().fields.keys()
|
||||
), 'Mismatch between {} list serializer & unified list serializer'.format(cls)
|
||||
assert set(list_serializer().fields.keys()) == set(unified_serializer().fields.keys()), (
|
||||
'Mismatch between {} list serializer & unified list serializer'.format(cls)
|
||||
)
|
||||
|
||||
|
||||
def test_unified_job_detail_exclusive_fields():
|
||||
|
||||
@@ -278,9 +278,9 @@ class TestSurveySpecValidation:
|
||||
({'type': 'password', 'default': ['some', 'invalid', 'list']}, 'expected to be string'),
|
||||
({'type': 'password', 'default': False}, 'expected to be string'),
|
||||
({'type': 'integer', 'default': 'foo'}, 'expected to be int'),
|
||||
({'type': 'integer', 'default': u'🐉'}, 'expected to be int'),
|
||||
({'type': 'integer', 'default': '🐉'}, 'expected to be int'),
|
||||
({'type': 'foo'}, 'allowed question types'),
|
||||
({'type': u'🐉'}, 'allowed question types'),
|
||||
({'type': '🐉'}, 'allowed question types'),
|
||||
({'type': 'multiplechoice'}, 'multiplechoice must specify choices'),
|
||||
({'type': 'integer', 'min': 'foo'}, 'min limit in survey question 0 expected to be integer'),
|
||||
({'question_name': 42}, "'question_name' in survey question 0 expected to be string."),
|
||||
|
||||
@@ -29,7 +29,7 @@ def hosts(ref_time):
|
||||
Host(name='host1', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
|
||||
Host(name='host2', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
|
||||
Host(name='host3', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
|
||||
Host(name=u'Iñtërnâtiônàlizætiøn', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
|
||||
Host(name='Iñtërnâtiônàlizætiøn', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ def test_clean_credential_insights():
|
||||
with pytest.raises(ValidationError) as e:
|
||||
proj.clean_credential()
|
||||
|
||||
assert json.dumps(str(e.value)) == json.dumps(str([u'Insights Credential is required for an Insights Project.']))
|
||||
assert json.dumps(str(e.value)) == json.dumps(str(['Insights Credential is required for an Insights Project.']))
|
||||
|
||||
@@ -96,8 +96,8 @@ class TestSurveyVariableValidation:
|
||||
def job(mocker):
|
||||
ret = mocker.MagicMock(
|
||||
**{
|
||||
'decrypted_extra_vars.return_value': '{\"secret_key\": \"my_password\"}',
|
||||
'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}',
|
||||
'decrypted_extra_vars.return_value': '{"secret_key": "my_password"}',
|
||||
'display_extra_vars.return_value': '{"secret_key": "$encrypted$"}',
|
||||
'extra_vars_dict': {"secret_key": "my_password"},
|
||||
'pk': 1,
|
||||
'job_template.pk': 1,
|
||||
@@ -313,7 +313,7 @@ class TestWorkflowSurveys:
|
||||
({'tmpl_var': 'bar2'}, False), # different value from template, not okay
|
||||
({'tmpl_var': 'bar', 'a': 2}, False), # extra key, not okay
|
||||
({'tmpl_var': 'bar', False: 2}, False), # Falsy key
|
||||
({'tmpl_var': 'bar', u'🐉': u'🐉'}, False), # dragons
|
||||
({'tmpl_var': 'bar', '🐉': '🐉'}, False), # dragons
|
||||
],
|
||||
)
|
||||
class TestExtraVarsNoPrompt:
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_valid__clean_extra_data_system_jobs(extra_data):
|
||||
)
|
||||
def test_invalid__extra_data_system_jobs(extra_data):
|
||||
accepted, rejected, errors = SystemJobTemplate(job_type='cleanup_jobs').accept_or_ignore_variables(extra_data)
|
||||
assert str(errors['extra_vars'][0]) == u'days must be a positive integer.'
|
||||
assert str(errors['extra_vars'][0]) == 'days must be a positive integer.'
|
||||
|
||||
|
||||
def test_unallowed_system_job_data():
|
||||
|
||||
@@ -7,9 +7,10 @@ import awx.main.notifications.rocketchat_backend as rocketchat_backend
|
||||
|
||||
|
||||
def test_send_messages():
|
||||
with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.rocketchat_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.rocketchat_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 201
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = rocketchat_backend.RocketChatBackend()
|
||||
@@ -92,9 +93,10 @@ def test_send_messages_with_icon_url():
|
||||
|
||||
|
||||
def test_send_messages_with_no_verify_ssl():
|
||||
with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.rocketchat_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.rocketchat_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 201
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = rocketchat_backend.RocketChatBackend(rocketchat_no_verify_ssl=True)
|
||||
|
||||
@@ -7,9 +7,10 @@ import awx.main.notifications.webhook_backend as webhook_backend
|
||||
|
||||
|
||||
def test_send_messages_as_POST():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('POST', None)
|
||||
@@ -38,9 +39,10 @@ def test_send_messages_as_POST():
|
||||
|
||||
|
||||
def test_send_messages_as_PUT():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.put.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('PUT', None)
|
||||
@@ -69,9 +71,10 @@ def test_send_messages_as_PUT():
|
||||
|
||||
|
||||
def test_send_messages_with_username():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('POST', None, username='userstring')
|
||||
@@ -100,9 +103,10 @@ def test_send_messages_with_username():
|
||||
|
||||
|
||||
def test_send_messages_with_password():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('POST', None, password='passwordstring')
|
||||
@@ -131,9 +135,10 @@ def test_send_messages_with_password():
|
||||
|
||||
|
||||
def test_send_messages_with_username_and_password():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('POST', None, username='userstring', password='passwordstring')
|
||||
@@ -162,9 +167,10 @@ def test_send_messages_with_username_and_password():
|
||||
|
||||
|
||||
def test_send_messages_with_no_verify_ssl():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('POST', None, disable_ssl_verification=True)
|
||||
@@ -193,9 +199,10 @@ def test_send_messages_with_no_verify_ssl():
|
||||
|
||||
|
||||
def test_send_messages_with_additional_headers():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
requests_mock.post.return_value.status_code = 200
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
backend = webhook_backend.WebhookBackend('POST', {'X-Test-Header1': 'test-content-1', 'X-Test-Header2': 'test-content-2'})
|
||||
@@ -229,9 +236,10 @@ def test_send_messages_with_additional_headers():
|
||||
|
||||
|
||||
def test_send_messages_with_redirects_ok():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
):
|
||||
# First two calls return redirects, third call returns 200
|
||||
requests_mock.post.side_effect = [
|
||||
mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
|
||||
@@ -266,9 +274,11 @@ def test_send_messages_with_redirects_ok():
|
||||
|
||||
|
||||
def test_send_messages_with_redirects_blank():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock,
|
||||
):
|
||||
# First call returns a redirect with Location header, second call returns 301 but NO Location header
|
||||
requests_mock.post.side_effect = [
|
||||
mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
|
||||
@@ -299,9 +309,11 @@ def test_send_messages_with_redirects_blank():
|
||||
|
||||
|
||||
def test_send_messages_with_redirects_max_retries_exceeded():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock,
|
||||
):
|
||||
# Return MAX_RETRIES (5) redirect responses to exceed the retry limit
|
||||
requests_mock.post.side_effect = [
|
||||
mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
|
||||
@@ -336,9 +348,11 @@ def test_send_messages_with_redirects_max_retries_exceeded():
|
||||
|
||||
|
||||
def test_send_messages_with_error_status_code():
|
||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
||||
) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock:
|
||||
with (
|
||||
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||
mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock,
|
||||
):
|
||||
# Return a 404 error status code
|
||||
requests_mock.post.return_value = mock.Mock(status_code=404)
|
||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||
|
||||
@@ -470,8 +470,7 @@ class TestIsWorkflowDone:
|
||||
True,
|
||||
smart_str(
|
||||
_(
|
||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
||||
" missing unified job template and error handling path []."
|
||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s) missing unified job template and error handling path []."
|
||||
).format(nodes[2].id, nodes[2].job.status)
|
||||
),
|
||||
)
|
||||
@@ -485,9 +484,9 @@ class TestIsWorkflowDone:
|
||||
assert g.has_workflow_failed() == (
|
||||
True,
|
||||
smart_str(
|
||||
_(
|
||||
"No error handling path for workflow job node(s) []. Workflow job node(s) missing" " unified job template and error handling path [{}]."
|
||||
).format(nodes[2].id)
|
||||
_("No error handling path for workflow job node(s) []. Workflow job node(s) missing unified job template and error handling path [{}].").format(
|
||||
nodes[2].id
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -501,9 +500,9 @@ class TestIsWorkflowDone:
|
||||
assert g.has_workflow_failed() == (
|
||||
True,
|
||||
smart_str(
|
||||
_(
|
||||
"No error handling path for workflow job node(s) []. Workflow job node(s) missing" " unified job template and error handling path [{}]."
|
||||
).format(nodes[0].id)
|
||||
_("No error handling path for workflow job node(s) []. Workflow job node(s) missing unified job template and error handling path [{}].").format(
|
||||
nodes[0].id
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -514,8 +513,7 @@ class TestIsWorkflowDone:
|
||||
True,
|
||||
smart_str(
|
||||
_(
|
||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
||||
" missing unified job template and error handling path []."
|
||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s) missing unified job template and error handling path []."
|
||||
).format(nodes[0].id, nodes[0].job.status)
|
||||
),
|
||||
)
|
||||
@@ -527,8 +525,7 @@ class TestIsWorkflowDone:
|
||||
True,
|
||||
smart_str(
|
||||
_(
|
||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
||||
" missing unified job template and error handling path []."
|
||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s) missing unified job template and error handling path []."
|
||||
).format(nodes[0].id, nodes[0].job.status)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -141,8 +141,8 @@ def test_cred_type_input_schema_validity(input_, valid):
|
||||
({'env': {'99': '{{awx_secret}}'}}, False),
|
||||
({'env': {'AWX_SECRET=': '{{awx_secret}}'}}, False),
|
||||
({'env': {'ANSIBLE_SETTING': '{{awx_secret}}'}}, False),
|
||||
({'env': {'DRAGON': u'🐉'}}, False),
|
||||
({'env': {u'🐉': 'DRAGON'}}, False),
|
||||
({'env': {'DRAGON': '🐉'}}, False),
|
||||
({'env': {'🐉': 'DRAGON'}}, False),
|
||||
({'extra_vars': 123}, False),
|
||||
({'extra_vars': {}}, True),
|
||||
({'extra_vars': {'hostname': '{{host}}'}}, True),
|
||||
@@ -306,7 +306,8 @@ def test_implicit_role_field_parents():
|
||||
second_field_descriptor = getattr(cls, field_name)
|
||||
# all supported linkage types
|
||||
assert isinstance(
|
||||
second_field_descriptor, (ReverseManyToOneDescriptor, ImplicitRoleDescriptor, ForwardManyToOneDescriptor) # not currently used
|
||||
second_field_descriptor,
|
||||
(ReverseManyToOneDescriptor, ImplicitRoleDescriptor, ForwardManyToOneDescriptor), # not currently used
|
||||
)
|
||||
# only these links are supported
|
||||
if field_attr:
|
||||
|
||||
@@ -36,7 +36,8 @@ uri = URI(scheme="https", username="myusername", password="mypasswordwith%40", h
|
||||
TEST_CLEARTEXT.append(
|
||||
{
|
||||
'uri': uri,
|
||||
'text': textwrap.dedent("""\
|
||||
'text': textwrap.dedent(
|
||||
"""\
|
||||
PLAY [all] ********************************************************************
|
||||
|
||||
TASK: [delete project directory before update] ********************************
|
||||
@@ -58,7 +59,9 @@ TEST_CLEARTEXT.append(
|
||||
|
||||
localhost : ok=0 changed=0 unreachable=0 failed=1
|
||||
|
||||
""" % (uri.username, uri.password, str(uri), str(uri))),
|
||||
"""
|
||||
% (uri.username, uri.password, str(uri), str(uri))
|
||||
),
|
||||
'host_occurrences': 2,
|
||||
}
|
||||
)
|
||||
@@ -67,7 +70,8 @@ uri = URI(scheme="https", username="Dhh3U47nmC26xk9PKscV", password="PXPfWW8YzYr
|
||||
TEST_CLEARTEXT.append(
|
||||
{
|
||||
'uri': uri,
|
||||
'text': textwrap.dedent("""\
|
||||
'text': textwrap.dedent(
|
||||
"""\
|
||||
TASK: [update project using git] **
|
||||
failed: [localhost] => {"cmd": "/usr/bin/git ls-remote https://REDACTED:********", "failed": true, "rc": 128}
|
||||
stderr: error: Couldn't resolve host '@%s' while accessing %s
|
||||
@@ -77,7 +81,9 @@ TEST_CLEARTEXT.append(
|
||||
msg: error: Couldn't resolve host '@%s' while accessing %s
|
||||
|
||||
fatal: HTTP request failed
|
||||
""" % (uri.host, str(uri), uri.host, str(uri))),
|
||||
"""
|
||||
% (uri.host, str(uri), uri.host, str(uri))
|
||||
),
|
||||
'host_occurrences': 4,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1303,10 +1303,10 @@ class TestInventoryUpdateCredentials(TestJobExecution):
|
||||
if credential:
|
||||
credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
|
||||
|
||||
env["VMWARE_USER"] == "bob",
|
||||
env["VMWARE_PASSWORD"] == "secret",
|
||||
env["VMWARE_HOST"] == "https://example.org",
|
||||
env["VMWARE_VALIDATE_CERTS"] == "False",
|
||||
(env["VMWARE_USER"] == "bob",)
|
||||
(env["VMWARE_PASSWORD"] == "secret",)
|
||||
(env["VMWARE_HOST"] == "https://example.org",)
|
||||
(env["VMWARE_VALIDATE_CERTS"] == "False",)
|
||||
|
||||
def test_azure_rm_source_with_tenant(self, private_data_dir, inventory_update, mocker, mock_me):
|
||||
task = jobs.RunInventoryUpdate()
|
||||
|
||||
@@ -163,13 +163,13 @@ def memoized_function(mocker, mock_cache):
|
||||
|
||||
def test_memoize_track_function(memoized_function, mock_cache):
|
||||
assert memoized_function('scott', 'scotterson') == 'scotterson'
|
||||
assert mock_cache.get('myfunction') == {u'scott-scotterson': 'scotterson'}
|
||||
assert mock_cache.get('myfunction') == {'scott-scotterson': 'scotterson'}
|
||||
assert memoized_function('scott', 'scotterson') == 'scotterson'
|
||||
|
||||
assert memoized_function.calls['scott'] == 1
|
||||
|
||||
assert memoized_function('john', 'smith') == 'smith'
|
||||
assert mock_cache.get('myfunction') == {u'scott-scotterson': 'scotterson', u'john-smith': 'smith'}
|
||||
assert mock_cache.get('myfunction') == {'scott-scotterson': 'scotterson', 'john-smith': 'smith'}
|
||||
assert memoized_function('john', 'smith') == 'smith'
|
||||
|
||||
assert memoized_function.calls['john'] == 1
|
||||
@@ -180,7 +180,7 @@ def test_memoize_delete(memoized_function, mock_cache):
|
||||
assert memoized_function('john', 'smith') == 'smith'
|
||||
assert memoized_function.calls['john'] == 1
|
||||
|
||||
assert mock_cache.get('myfunction') == {u'john-smith': 'smith'}
|
||||
assert mock_cache.get('myfunction') == {'john-smith': 'smith'}
|
||||
|
||||
with mock.patch('awx.main.utils.common.memoize_delete', side_effect=mock_cache.delete):
|
||||
common.memoize_delete('myfunction')
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_encrypt_field_without_pk():
|
||||
|
||||
|
||||
def test_encrypt_field_with_unicode_string():
|
||||
value = u'Iñtërnâtiônàlizætiøn'
|
||||
value = 'Iñtërnâtiônàlizætiøn'
|
||||
field = Setting(value=value)
|
||||
encrypted = field.value = encryption.encrypt_field(field, 'value')
|
||||
assert encryption.decrypt_field(field, 'value') == value
|
||||
|
||||
@@ -74,16 +74,16 @@ class TestSmartFilterQueryFromString:
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})),
|
||||
('"facts__facts__ space "="f"', Q(**{u"facts__facts__ space ": u"f"})),
|
||||
('"facts__facts__ e "=no_quotes_here', Q(**{u"facts__facts__ e ": u"no_quotes_here"})),
|
||||
('a__b__c=3', Q(**{u"a__b__c": 3})),
|
||||
('a__b__c=3.14', Q(**{u"a__b__c": 3.14})),
|
||||
('a__b__c=true', Q(**{u"a__b__c": True})),
|
||||
('a__b__c=false', Q(**{u"a__b__c": False})),
|
||||
('a__b__c=null', Q(**{u"a__b__c": None})),
|
||||
('ansible_facts__a="true"', Q(**{u"ansible_facts__contains": {u"a": u"true"}})),
|
||||
('ansible_facts__a__exact="true"', Q(**{u"ansible_facts__contains": {u"a": u"true"}})),
|
||||
('facts__facts__blank=""', Q(**{"facts__facts__blank": ""})),
|
||||
('"facts__facts__ space "="f"', Q(**{"facts__facts__ space ": "f"})),
|
||||
('"facts__facts__ e "=no_quotes_here', Q(**{"facts__facts__ e ": "no_quotes_here"})),
|
||||
('a__b__c=3', Q(**{"a__b__c": 3})),
|
||||
('a__b__c=3.14', Q(**{"a__b__c": 3.14})),
|
||||
('a__b__c=true', Q(**{"a__b__c": True})),
|
||||
('a__b__c=false', Q(**{"a__b__c": False})),
|
||||
('a__b__c=null', Q(**{"a__b__c": None})),
|
||||
('ansible_facts__a="true"', Q(**{"ansible_facts__contains": {"a": "true"}})),
|
||||
('ansible_facts__a__exact="true"', Q(**{"ansible_facts__contains": {"a": "true"}})),
|
||||
# ('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
|
||||
# ('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})),
|
||||
],
|
||||
@@ -101,7 +101,7 @@ class TestSmartFilterQueryFromString:
|
||||
def test_invalid_filter_strings(self, mock_get_host_model, filter_string):
|
||||
with pytest.raises(RuntimeError) as e:
|
||||
SmartFilter.query_from_string(filter_string)
|
||||
assert str(e.value) == u"Invalid query " + filter_string
|
||||
assert str(e.value) == "Invalid query " + filter_string
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string",
|
||||
@@ -118,8 +118,8 @@ class TestSmartFilterQueryFromString:
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
(u'(a=abc\u1f5e3def)', Q(**{u"a": u"abc\u1f5e3def"})),
|
||||
(u'(ansible_facts__a=abc\u1f5e3def)', Q(**{u"ansible_facts__contains": {u"a": u"abc\u1f5e3def"}})),
|
||||
('(a=abc\u1f5e3def)', Q(**{"a": "abc\u1f5e3def"})),
|
||||
('(ansible_facts__a=abc\u1f5e3def)', Q(**{"ansible_facts__contains": {"a": "abc\u1f5e3def"}})),
|
||||
],
|
||||
)
|
||||
def test_unicode(self, mock_get_host_model, filter_string, q_expected):
|
||||
@@ -129,17 +129,14 @@ class TestSmartFilterQueryFromString:
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
('(a=b)', Q(**{u"a": u"b"})),
|
||||
('a=b and c=d', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})),
|
||||
('(a=b and c=d)', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})),
|
||||
('a=b or c=d', Q(**{u"a": u"b"}) | Q(**{u"c": u"d"})),
|
||||
('(a=b and c=d) or (e=f)', (Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})) | (Q(**{u"e": u"f"}))),
|
||||
('(a=b)', Q(**{"a": "b"})),
|
||||
('a=b and c=d', Q(**{"a": "b"}) & Q(**{"c": "d"})),
|
||||
('(a=b and c=d)', Q(**{"a": "b"}) & Q(**{"c": "d"})),
|
||||
('a=b or c=d', Q(**{"a": "b"}) | Q(**{"c": "d"})),
|
||||
('(a=b and c=d) or (e=f)', (Q(**{"a": "b"}) & Q(**{"c": "d"})) | (Q(**{"e": "f"}))),
|
||||
(
|
||||
'a=b or a=d or a=e or a=z and b=h and b=i and b=j and b=k',
|
||||
Q(**{u"a": u"b"})
|
||||
| Q(**{u"a": u"d"})
|
||||
| Q(**{u"a": u"e"})
|
||||
| Q(**{u"a": u"z"}) & Q(**{u"b": u"h"}) & Q(**{u"b": u"i"}) & Q(**{u"b": u"j"}) & Q(**{u"b": u"k"}),
|
||||
Q(**{"a": "b"}) | Q(**{"a": "d"}) | Q(**{"a": "e"}) | Q(**{"a": "z"}) & Q(**{"b": "h"}) & Q(**{"b": "i"}) & Q(**{"b": "j"}) & Q(**{"b": "k"}),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -150,21 +147,21 @@ class TestSmartFilterQueryFromString:
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
('ansible_facts__a__b__c[]=3', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [3]}}}})),
|
||||
('ansible_facts__a__b__c[]=3.14', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [3.14]}}}})),
|
||||
('ansible_facts__a__b__c[]=true', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [True]}}}})),
|
||||
('ansible_facts__a__b__c[]=false', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [False]}}}})),
|
||||
('ansible_facts__a__b__c[]="true"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [u"true"]}}}})),
|
||||
('ansible_facts__a__b__c[]="hello world"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [u"hello world"]}}}})),
|
||||
('ansible_facts__a__b__c[]__d[]="foobar"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": [u"foobar"]}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d="foobar"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": u"foobar"}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d__e="foobar"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": {u"e": u"foobar"}}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d__e[]="foobar"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": {u"e": [u"foobar"]}}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d__e__f[]="foobar"', Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": {u"e": {u"f": [u"foobar"]}}}]}}}})),
|
||||
('ansible_facts__a__b__c[]=3', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [3]}}}})),
|
||||
('ansible_facts__a__b__c[]=3.14', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [3.14]}}}})),
|
||||
('ansible_facts__a__b__c[]=true', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [True]}}}})),
|
||||
('ansible_facts__a__b__c[]=false', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [False]}}}})),
|
||||
('ansible_facts__a__b__c[]="true"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": ["true"]}}}})),
|
||||
('ansible_facts__a__b__c[]="hello world"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": ["hello world"]}}}})),
|
||||
('ansible_facts__a__b__c[]__d[]="foobar"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": ["foobar"]}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d="foobar"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": "foobar"}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d__e="foobar"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": "foobar"}}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d__e[]="foobar"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": ["foobar"]}}]}}}})),
|
||||
('ansible_facts__a__b__c[]__d__e__f[]="foobar"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": {"f": ["foobar"]}}}]}}}})),
|
||||
(
|
||||
'(ansible_facts__a__b__c[]__d__e__f[]="foobar") and (ansible_facts__a__b__c[]__d__e[]="foobar")',
|
||||
Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": {u"e": {u"f": [u"foobar"]}}}]}}}})
|
||||
& Q(**{u"ansible_facts__contains": {u"a": {u"b": {u"c": [{u"d": {u"e": [u"foobar"]}}]}}}}),
|
||||
Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": {"f": ["foobar"]}}}]}}}})
|
||||
& Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": ["foobar"]}}]}}}}),
|
||||
),
|
||||
# ('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
|
||||
# ('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})),
|
||||
@@ -178,7 +175,7 @@ class TestSmartFilterQueryFromString:
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
# ('a__b__c[]="true"', Q(**{u"a__b__c__contains": u"\"true\""})),
|
||||
('ansible_facts__a="true"', Q(**{u"ansible_facts__contains": {u"a": u"true"}})),
|
||||
('ansible_facts__a="true"', Q(**{"ansible_facts__contains": {"a": "true"}})),
|
||||
# ('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
|
||||
# ('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})),
|
||||
],
|
||||
@@ -190,8 +187,8 @@ class TestSmartFilterQueryFromString:
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
('ansible_facts__a=null', Q(**{u"ansible_facts__contains": {u"a": None}})),
|
||||
('ansible_facts__c="null"', Q(**{u"ansible_facts__contains": {u"c": u"\"null\""}})),
|
||||
('ansible_facts__a=null', Q(**{"ansible_facts__contains": {"a": None}})),
|
||||
('ansible_facts__c="null"', Q(**{"ansible_facts__contains": {"c": "\"null\""}})),
|
||||
],
|
||||
)
|
||||
def test_contains_query_generated_null(self, mock_get_host_model, filter_string, q_expected):
|
||||
@@ -201,17 +198,17 @@ class TestSmartFilterQueryFromString:
|
||||
@pytest.mark.parametrize(
|
||||
"filter_string,q_expected",
|
||||
[
|
||||
('group__search=foo', Q(Q(**{u"group__name__icontains": u"foo"}) | Q(**{u"group__description__icontains": u"foo"}))),
|
||||
('group__search=foo', Q(Q(**{"group__name__icontains": "foo"}) | Q(**{"group__description__icontains": "foo"}))),
|
||||
(
|
||||
'search=foo and group__search=foo',
|
||||
Q(
|
||||
Q(**{u"name__icontains": u"foo"}) | Q(**{u"description__icontains": u"foo"}),
|
||||
Q(**{u"group__name__icontains": u"foo"}) | Q(**{u"group__description__icontains": u"foo"}),
|
||||
Q(**{"name__icontains": "foo"}) | Q(**{"description__icontains": "foo"}),
|
||||
Q(**{"group__name__icontains": "foo"}) | Q(**{"group__description__icontains": "foo"}),
|
||||
),
|
||||
),
|
||||
(
|
||||
'search=foo or ansible_facts__a=null',
|
||||
Q(Q(**{u"name__icontains": u"foo"}) | Q(**{u"description__icontains": u"foo"})) | Q(**{u"ansible_facts__contains": {u"a": None}}),
|
||||
Q(Q(**{"name__icontains": "foo"}) | Q(**{"description__icontains": "foo"})) | Q(**{"ansible_facts__contains": {"a": None}}),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ def test_kv_unsafe():
|
||||
|
||||
|
||||
def test_kv_unsafe_unicode():
|
||||
assert safe_dump({'a': u'🐉'}) == '!unsafe \'a\': !unsafe "\\U0001F409"\n'
|
||||
assert safe_dump({'a': '🐉'}) == '!unsafe \'a\': !unsafe "\\U0001F409"\n'
|
||||
|
||||
|
||||
def test_kv_unsafe_in_list():
|
||||
|
||||
@@ -14,16 +14,14 @@ def test_validate_rh_basic_auth_rhsm():
|
||||
"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
||||
licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'
|
||||
) as mock_get_host, patch.object(licenser, 'get_rhsm_subs', return_value=[]) as mock_get_rhsm, patch.object(
|
||||
licenser, 'get_satellite_subs'
|
||||
) as mock_get_satellite, patch.object(
|
||||
licenser, 'get_crc_subs'
|
||||
) as mock_get_crc, patch.object(
|
||||
licenser, 'generate_license_options_from_entitlements'
|
||||
) as mock_generate:
|
||||
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com') as mock_get_host,
|
||||
patch.object(licenser, 'get_rhsm_subs', return_value=[]) as mock_get_rhsm,
|
||||
patch.object(licenser, 'get_satellite_subs') as mock_get_satellite,
|
||||
patch.object(licenser, 'get_crc_subs') as mock_get_crc,
|
||||
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||
):
|
||||
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||
|
||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
||||
@@ -44,16 +42,14 @@ def test_validate_rh_basic_auth_satellite():
|
||||
"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
||||
licenser, 'get_host_from_rhsm_config', return_value='https://satellite.example.com'
|
||||
) as mock_get_host, patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm, patch.object(
|
||||
licenser, 'get_satellite_subs', return_value=[]
|
||||
) as mock_get_satellite, patch.object(
|
||||
licenser, 'get_crc_subs'
|
||||
) as mock_get_crc, patch.object(
|
||||
licenser, 'generate_license_options_from_entitlements'
|
||||
) as mock_generate:
|
||||
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://satellite.example.com') as mock_get_host,
|
||||
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||
patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite,
|
||||
patch.object(licenser, 'get_crc_subs') as mock_get_crc,
|
||||
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||
):
|
||||
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||
|
||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
||||
@@ -72,14 +68,14 @@ def test_validate_rh_service_account_crc():
|
||||
"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host, patch.object(
|
||||
licenser, 'get_rhsm_subs'
|
||||
) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs') as mock_get_satellite, patch.object(
|
||||
licenser, 'get_crc_subs', return_value=[]
|
||||
) as mock_get_crc, patch.object(
|
||||
licenser, 'generate_license_options_from_entitlements'
|
||||
) as mock_generate:
|
||||
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host,
|
||||
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||
patch.object(licenser, 'get_satellite_subs') as mock_get_satellite,
|
||||
patch.object(licenser, 'get_crc_subs', return_value=[]) as mock_get_crc,
|
||||
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||
):
|
||||
mock_settings.SUBSCRIPTIONS_RHSM_URL = 'https://console.redhat.com/api/rhsm/v1/subscriptions'
|
||||
|
||||
licenser.validate_rh('client_id', 'client_secret', basic_auth=False)
|
||||
@@ -99,14 +95,14 @@ def test_validate_rh_candlepin_host_prioritized_over_rhsm_config():
|
||||
"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host, patch.object(
|
||||
licenser, 'get_rhsm_subs'
|
||||
) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite, patch.object(
|
||||
licenser, 'get_crc_subs'
|
||||
) as mock_get_crc, patch.object(
|
||||
licenser, 'generate_license_options_from_entitlements'
|
||||
) as mock_generate:
|
||||
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host,
|
||||
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||
patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite,
|
||||
patch.object(licenser, 'get_crc_subs') as mock_get_crc,
|
||||
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||
):
|
||||
mock_settings.REDHAT_CANDLEPIN_HOST = 'https://satellite.example.com'
|
||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
||||
|
||||
@@ -121,14 +117,14 @@ def test_validate_rh_prepends_scheme_when_missing():
|
||||
"""REDHAT_CANDLEPIN_HOST without a scheme gets https:// prepended"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(licenser, 'get_host_from_rhsm_config'), patch.object(
|
||||
licenser, 'get_rhsm_subs'
|
||||
) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite, patch.object(
|
||||
licenser, 'get_crc_subs'
|
||||
), patch.object(
|
||||
licenser, 'generate_license_options_from_entitlements'
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config'),
|
||||
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||
patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite,
|
||||
patch.object(licenser, 'get_crc_subs'),
|
||||
patch.object(licenser, 'generate_license_options_from_entitlements'),
|
||||
):
|
||||
|
||||
mock_settings.REDHAT_CANDLEPIN_HOST = 'satellite.example.com'
|
||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
||||
|
||||
@@ -140,8 +136,9 @@ def test_validate_rh_missing_user_raises_error():
|
||||
"""Test validate_rh raises ValueError when user is missing"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
||||
licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'),
|
||||
):
|
||||
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||
with pytest.raises(ValueError, match='subscriptions_client_id or subscriptions_username is required'):
|
||||
@@ -152,8 +149,9 @@ def test_validate_rh_missing_password_raises_error():
|
||||
"""Test validate_rh raises ValueError when password is missing"""
|
||||
licenser = Licenser()
|
||||
|
||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
||||
licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'
|
||||
with (
|
||||
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||
patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'),
|
||||
):
|
||||
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||
with pytest.raises(ValueError, match='subscriptions_client_secret or subscriptions_password is required'):
|
||||
|
||||
@@ -333,16 +333,16 @@ def update_scm_url(scm_type, url, username=True, password=True, check_special_ca
|
||||
netloc_password = ''
|
||||
|
||||
if netloc_username and parts.scheme != 'file' and scm_type not in ("insights", "archive"):
|
||||
netloc = u':'.join([urllib.parse.quote(x, safe='') for x in (netloc_username, netloc_password) if x])
|
||||
netloc = ':'.join([urllib.parse.quote(x, safe='') for x in (netloc_username, netloc_password) if x])
|
||||
else:
|
||||
netloc = u''
|
||||
netloc = ''
|
||||
# urllib.parse strips brackets from IPv6 addresses, so we need to add them back in
|
||||
hostname = parts.hostname
|
||||
if hostname and ':' in hostname and '[' in url and ']' in url:
|
||||
hostname = f'[{hostname}]'
|
||||
netloc = u'@'.join(filter(None, [netloc, hostname]))
|
||||
netloc = '@'.join(filter(None, [netloc, hostname]))
|
||||
if parts.port:
|
||||
netloc = u':'.join([netloc, str(parts.port)])
|
||||
netloc = ':'.join([netloc, str(parts.port)])
|
||||
new_url = urllib.parse.urlunsplit([parts.scheme, netloc, parts.path, parts.query, parts.fragment])
|
||||
if scp_format and parts.scheme == 'git+ssh':
|
||||
new_url = new_url.replace('git+ssh://', '', 1).replace('/', ':', 1)
|
||||
@@ -378,7 +378,7 @@ def _convert_model_field_for_display(obj, field_name, password_fields=None):
|
||||
if password_fields is None:
|
||||
password_fields = set(getattr(type(obj), 'PASSWORD_FIELDS', [])) | set(['password'])
|
||||
if field_name in password_fields or (isinstance(field_val, str) and field_val.startswith('$encrypted$')):
|
||||
return u'hidden'
|
||||
return 'hidden'
|
||||
if hasattr(obj, 'display_%s' % field_name):
|
||||
field_val = getattr(obj, 'display_%s' % field_name)()
|
||||
if isinstance(field_val, (list, dict)):
|
||||
@@ -1126,15 +1126,15 @@ def truncate_stdout(stdout, size):
|
||||
if size <= 0 or len(stdout) <= size:
|
||||
return stdout
|
||||
|
||||
stdout = stdout[: (size - 1)] + u'\u2026'
|
||||
stdout = stdout[: (size - 1)] + '\u2026'
|
||||
set_count, reset_count = 0, 0
|
||||
for m in ANSI_SGR_PATTERN.finditer(stdout):
|
||||
if m.group() == u'\u001b[0m':
|
||||
if m.group() == '\u001b[0m':
|
||||
reset_count += 1
|
||||
else:
|
||||
set_count += 1
|
||||
|
||||
return stdout + u'\u001b[0m' * (set_count - reset_count)
|
||||
return stdout + '\u001b[0m' * (set_count - reset_count)
|
||||
|
||||
|
||||
def deepmerge(a, b):
|
||||
|
||||
@@ -46,7 +46,6 @@ def db_requirement_violations() -> Optional[str]:
|
||||
if os.getenv('SKIP_PG_VERSION_CHECK', False):
|
||||
return None
|
||||
if connection.vendor == 'postgresql':
|
||||
|
||||
# enforce the postgres version is a minimum of 12 (we need this for partitioning); if not, then terminate program with exit code of 1
|
||||
# In the future if we require a feature of a version of postgres > 12 this should be updated to reflect that.
|
||||
# The return of connection.pg_version is something like 12013
|
||||
|
||||
+10
-10
@@ -118,11 +118,11 @@ class DynamicLevelFilter(Filter):
|
||||
|
||||
|
||||
def string_to_type(t):
|
||||
if t == u'null':
|
||||
if t == 'null':
|
||||
return None
|
||||
if t == u'true':
|
||||
if t == 'true':
|
||||
return True
|
||||
elif t == u'false':
|
||||
elif t == 'false':
|
||||
return False
|
||||
|
||||
if re.search(r'^[-+]?[0-9]+$', t):
|
||||
@@ -151,7 +151,7 @@ class SmartFilter(object):
|
||||
search_kwargs = self._expand_search(k, v)
|
||||
if search_kwargs:
|
||||
kwargs.update(search_kwargs)
|
||||
q = reduce(lambda x, y: x | y, [models.Q(**{u'%s__icontains' % _k: _v}) for _k, _v in kwargs.items()])
|
||||
q = reduce(lambda x, y: x | y, [models.Q(**{'%s__icontains' % _k: _v}) for _k, _v in kwargs.items()])
|
||||
self.result = Host.objects.filter(q)
|
||||
else:
|
||||
# detect loops and restrict access to sensitive fields
|
||||
@@ -168,7 +168,7 @@ class SmartFilter(object):
|
||||
return v
|
||||
|
||||
def strip_quotes_json_logic(self, v):
|
||||
if type(v) is str and v.startswith('"') and v.endswith('"') and v != u'"null"':
|
||||
if type(v) is str and v.startswith('"') and v.endswith('"') and v != '"null"':
|
||||
return v[1:-1]
|
||||
return v
|
||||
|
||||
@@ -203,9 +203,9 @@ class SmartFilter(object):
|
||||
strip_len = len(SmartFilter.SEARCHABLE_RELATIONSHIP)
|
||||
k = k[strip_len:]
|
||||
|
||||
pieces = k.split(u'__')
|
||||
pieces = k.split('__')
|
||||
|
||||
assembled_k = u'%s__contains' % (SmartFilter.SEARCHABLE_RELATIONSHIP)
|
||||
assembled_k = '%s__contains' % (SmartFilter.SEARCHABLE_RELATIONSHIP)
|
||||
assembled_v = None
|
||||
|
||||
last_v = None
|
||||
@@ -213,7 +213,7 @@ class SmartFilter(object):
|
||||
|
||||
for i, piece in enumerate(pieces):
|
||||
new_kv = dict()
|
||||
if piece.endswith(u'[]'):
|
||||
if piece.endswith('[]'):
|
||||
new_v = []
|
||||
new_kv[piece[0:-2]] = new_v
|
||||
else:
|
||||
@@ -241,7 +241,7 @@ class SmartFilter(object):
|
||||
|
||||
def _extract_key_value(self, t):
|
||||
k = t[0]
|
||||
v = t[1] if len(t) > 1 else u""
|
||||
v = t[1] if len(t) > 1 else ""
|
||||
|
||||
# Strip quotes from key
|
||||
if isinstance(k, str) and k.startswith('"') and k.endswith('"'):
|
||||
@@ -333,7 +333,7 @@ class SmartFilter(object):
|
||||
try:
|
||||
res = bool_expr.parse_string(filter_string, parse_all=True)
|
||||
except (pp.ParseException, FieldDoesNotExist):
|
||||
raise RuntimeError(u"Invalid query %s" % filter_string_raw)
|
||||
raise RuntimeError("Invalid query %s" % filter_string_raw)
|
||||
|
||||
if len(res) > 0:
|
||||
return res[0].result
|
||||
|
||||
@@ -265,9 +265,7 @@ class Licenser(object):
|
||||
except requests.exceptions.ConnectionError as error:
|
||||
raise error
|
||||
except OSError as error:
|
||||
raise OSError(
|
||||
'Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)
|
||||
) from error # noqa
|
||||
raise OSError('Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)) from error # noqa
|
||||
subs.raise_for_status()
|
||||
|
||||
for sub in subs.json():
|
||||
@@ -328,9 +326,7 @@ class Licenser(object):
|
||||
except requests.exceptions.ConnectionError as error:
|
||||
raise error
|
||||
except OSError as error:
|
||||
raise OSError(
|
||||
'Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)
|
||||
) from error # noqa
|
||||
raise OSError('Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)) from error # noqa
|
||||
orgs.raise_for_status()
|
||||
|
||||
for org in orgs.json()['results']:
|
||||
|
||||
@@ -134,7 +134,7 @@ class MemInventory(object):
|
||||
try:
|
||||
port = int(name.split(':')[1])
|
||||
except (ValueError, UnicodeDecodeError):
|
||||
logger.warning(u'Invalid port "%s" for host "%s"', name.split(':')[1], host_name)
|
||||
logger.warning('Invalid port "%s" for host "%s"', name.split(':')[1], host_name)
|
||||
port = None
|
||||
else:
|
||||
host_name = name
|
||||
|
||||
@@ -20,7 +20,7 @@ class SafeLoader(yaml.Loader):
|
||||
return node
|
||||
|
||||
|
||||
SafeLoader.add_constructor(u'!unsafe', SafeLoader.construct_yaml_unsafe)
|
||||
SafeLoader.add_constructor('!unsafe', SafeLoader.construct_yaml_unsafe)
|
||||
|
||||
|
||||
def safe_dump(x, safe_dict=None):
|
||||
|
||||
@@ -39,7 +39,7 @@ class AdHocCommand(HasCreate, UnifiedJob):
|
||||
verbosity=verbosity,
|
||||
inventory=self.ds.inventory,
|
||||
credential=self.ds.credential,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
@@ -53,7 +53,7 @@ class AdHocCommand(HasCreate, UnifiedJob):
|
||||
verbosity=verbosity,
|
||||
inventory=inventory,
|
||||
credential=credential,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
return self.update_identity(AdHocCommands(self.connection).post(payload))
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class ExecutionEnvironment(HasCreate, HasCopy, base.Base):
|
||||
organization=organization.id if organization else None,
|
||||
credential=credential.id if credential else None,
|
||||
pull=pull,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return payload
|
||||
|
||||
@@ -70,7 +70,7 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
scm_url=scm_url,
|
||||
scm_branch=scm_branch,
|
||||
credential=credential,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
@@ -84,7 +84,7 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
scm_branch=scm_branch,
|
||||
organization=organization,
|
||||
credential=credential,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
self.update_identity(Projects(self.connection).post(payload))
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ class CLI(object):
|
||||
self.root.connection.login(username, password)
|
||||
self.root.get()
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Basic authentication failed: {str(e)}. " "Verify credentials and network connectivity.") from e
|
||||
raise RuntimeError(f"Basic authentication failed: {str(e)}. Verify credentials and network connectivity.") from e
|
||||
return
|
||||
|
||||
# Use session-based authentication (default)
|
||||
|
||||
@@ -174,7 +174,7 @@ class ProjectCreate(CustomAction):
|
||||
resource = 'projects'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
parser.choices[self.action].add_argument('--monitor', action='store_true', help=('If set, prints stdout of the project update until ' 'it finishes.'))
|
||||
parser.choices[self.action].add_argument('--monitor', action='store_true', help=('If set, prints stdout of the project update until it finishes.'))
|
||||
parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the new project has updated.')
|
||||
|
||||
def post(self, kwargs):
|
||||
|
||||
@@ -42,9 +42,7 @@ class CircularDependencyError(ValueError):
|
||||
def __init__(self, data):
|
||||
# Sort the data just to make the output consistent, for use in
|
||||
# error messages. That's convenient for doctests.
|
||||
s = 'Circular dependencies exist among these items: {{{}}}'.format(
|
||||
', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))
|
||||
) # noqa
|
||||
s = 'Circular dependencies exist among these items: {{{}}}'.format(', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))) # noqa
|
||||
super(CircularDependencyError, self).__init__(s)
|
||||
self.data = data
|
||||
|
||||
|
||||
Reference in New Issue
Block a user