mirror of
https://github.com/ansible/awx.git
synced 2026-10-31 14:36:17 -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)
|
ret = super(LoggedBasicAuthentication, self).authenticate(request)
|
||||||
if ret:
|
if ret:
|
||||||
username = ret[0].username if ret[0] else '<none>'
|
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
|
return ret
|
||||||
|
|
||||||
def authenticate_header(self, request):
|
def authenticate_header(self, request):
|
||||||
|
|||||||
+2
-3
@@ -81,7 +81,6 @@ analytics_logger = logging.getLogger('awx.analytics.performance')
|
|||||||
|
|
||||||
|
|
||||||
class LoggedLoginView(auth_views.LoginView):
|
class LoggedLoginView(auth_views.LoginView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if is_proxied_request():
|
if is_proxied_request():
|
||||||
next = request.GET.get('next', "")
|
next = request.GET.get('next', "")
|
||||||
@@ -116,7 +115,7 @@ class LoggedLoginView(auth_views.LoginView):
|
|||||||
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
||||||
ip = get_remote_host(request) # request.META.get('REMOTE_ADDR', None)
|
ip = get_remote_host(request) # request.META.get('REMOTE_ADDR', None)
|
||||||
if request.user.is_authenticated:
|
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(
|
ret.set_cookie(
|
||||||
'userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False), samesite=getattr(settings, 'USER_COOKIE_SAMESITE', 'Lax')
|
'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
|
return ret
|
||||||
else:
|
else:
|
||||||
if 'username' in self.request.POST:
|
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
|
ret.status_code = 401
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -171,8 +171,7 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
):
|
):
|
||||||
field_info['type'] = 'json'
|
field_info['type'] = 'json'
|
||||||
elif (
|
elif (
|
||||||
isinstance(field, ManyRelatedField)
|
isinstance(field, ManyRelatedField) and field.field_name == 'credentials'
|
||||||
and field.field_name == 'credentials'
|
|
||||||
# launch-time credentials
|
# launch-time credentials
|
||||||
):
|
):
|
||||||
field_info['type'] = 'list_of_ids'
|
field_info['type'] = 'list_of_ids'
|
||||||
|
|||||||
+3
-3
@@ -99,9 +99,9 @@ class CustomAutoSchema(AutoSchema):
|
|||||||
except Exception:
|
except Exception:
|
||||||
serializer = None
|
serializer = None
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'{}.get_serializer() raised an exception during '
|
'{}.get_serializer() raised an exception during schema generation. Serializer fields will not be generated for this view.'.format(
|
||||||
'schema generation. Serializer fields will not be '
|
self.view.__class__.__name__
|
||||||
'generated for this view.'.format(self.view.__class__.__name__)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if hasattr(self.view, 'swagger_topic'):
|
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.
|
# Don't allow assigning a local_path when scm_type is set.
|
||||||
valid_local_paths = Project.get_local_path_choices()
|
valid_local_paths = Project.get_local_path_choices()
|
||||||
if self.instance:
|
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:
|
else:
|
||||||
scm_type = attrs.get('scm_type', u'') or u''
|
scm_type = attrs.get('scm_type', '') or ''
|
||||||
if self.instance and not scm_type:
|
if self.instance and not scm_type:
|
||||||
valid_local_paths.append(self.instance.local_path)
|
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']:
|
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:
|
if port < 1 or port > 65535:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except 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
|
return name, port
|
||||||
|
|
||||||
def validate_name(self, value):
|
def validate_name(self, value):
|
||||||
@@ -3595,7 +3595,7 @@ class JobRelaunchSerializer(BaseSerializer):
|
|||||||
res = super(JobRelaunchSerializer, self).to_representation(obj)
|
res = super(JobRelaunchSerializer, self).to_representation(obj)
|
||||||
view = self.context.get('view', None)
|
view = self.context.get('view', None)
|
||||||
if hasattr(view, '_raw_data_form_marker'):
|
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)
|
res.update(password_keys)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -3765,7 +3765,7 @@ class AdHocCommandRelaunchSerializer(AdHocCommandSerializer):
|
|||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
if 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:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|||||||
@@ -1768,11 +1768,15 @@ class CredentialExternalTest(OIDCCredentialTestMixin, SubDetailAPIView):
|
|||||||
obj_permission_type = 'use'
|
obj_permission_type = 'use'
|
||||||
resource_purpose = 'test external credential'
|
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
|
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,
|
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.
|
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):
|
def post(self, request, *args, **kwargs):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
if obj.credential_type.kind != 'external':
|
if obj.credential_type.kind != 'external':
|
||||||
@@ -2692,11 +2696,11 @@ class JobTemplateLaunch(RetrieveAPIView):
|
|||||||
if needed_passwords:
|
if needed_passwords:
|
||||||
data['credential_passwords'] = {}
|
data['credential_passwords'] = {}
|
||||||
for p in needed_passwords:
|
for p in needed_passwords:
|
||||||
data['credential_passwords'][p] = u''
|
data['credential_passwords'][p] = ''
|
||||||
else:
|
else:
|
||||||
data.pop('credential_passwords')
|
data.pop('credential_passwords')
|
||||||
for v in obj.variables_needed_to_start:
|
for v in obj.variables_needed_to_start:
|
||||||
extra_vars.setdefault(v, u'')
|
extra_vars.setdefault(v, '')
|
||||||
if extra_vars:
|
if extra_vars:
|
||||||
data['extra_vars'] = extra_vars
|
data['extra_vars'] = extra_vars
|
||||||
modified_ask_mapping = models.JobTemplate.get_ask_mapping()
|
modified_ask_mapping = models.JobTemplate.get_ask_mapping()
|
||||||
@@ -3347,7 +3351,7 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su
|
|||||||
'''
|
'''
|
||||||
relationships = ['success_nodes', 'failure_nodes', 'always_nodes']
|
relationships = ['success_nodes', 'failure_nodes', 'always_nodes']
|
||||||
relationships.remove(self.relationship)
|
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():
|
if models.WorkflowJobTemplateNode.objects.filter(Q(pk=parent.id) & qs).exists():
|
||||||
return {"Error": _("Relationship not allowed.")}
|
return {"Error": _("Relationship not allowed.")}
|
||||||
@@ -3548,7 +3552,7 @@ class WorkflowJobTemplateLaunch(RetrieveAPIView):
|
|||||||
extra_vars = data.pop('extra_vars', None) or {}
|
extra_vars = data.pop('extra_vars', None) or {}
|
||||||
if obj:
|
if obj:
|
||||||
for v in obj.variables_needed_to_start:
|
for v in obj.variables_needed_to_start:
|
||||||
extra_vars.setdefault(v, u'')
|
extra_vars.setdefault(v, '')
|
||||||
if extra_vars:
|
if extra_vars:
|
||||||
data['extra_vars'] = extra_vars
|
data['extra_vars'] = extra_vars
|
||||||
modified_ask_mapping = models.WorkflowJobTemplate.get_ask_mapping()
|
modified_ask_mapping = models.WorkflowJobTemplate.get_ask_mapping()
|
||||||
@@ -3958,7 +3962,7 @@ class JobRelaunch(RetrieveAPIView):
|
|||||||
if needed_passwords:
|
if needed_passwords:
|
||||||
data['credential_passwords'] = {}
|
data['credential_passwords'] = {}
|
||||||
for p in needed_passwords:
|
for p in needed_passwords:
|
||||||
data['credential_passwords'][p] = u''
|
data['credential_passwords'][p] = ''
|
||||||
else:
|
else:
|
||||||
data.pop('credential_passwords', None)
|
data.pop('credential_passwords', None)
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class ApiV2SubscriptionView(APIView):
|
|||||||
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
||||||
msg = exc.args[0]
|
msg = exc.args[0]
|
||||||
else:
|
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({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
return Response(validated)
|
return Response(validated)
|
||||||
@@ -298,7 +298,7 @@ class ApiV2AttachView(APIView):
|
|||||||
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
||||||
msg = exc.args[0]
|
msg = exc.args[0]
|
||||||
else:
|
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({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
for sub in validated:
|
for sub in validated:
|
||||||
@@ -381,7 +381,7 @@ class ApiV2ConfigView(APIView):
|
|||||||
try:
|
try:
|
||||||
data_actual = json.dumps(request.data)
|
data_actual = json.dumps(request.data)
|
||||||
except Exception:
|
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)
|
return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
license_data = json.loads(data_actual)
|
license_data = json.loads(data_actual)
|
||||||
@@ -406,7 +406,7 @@ class ApiV2ConfigView(APIView):
|
|||||||
license_data_validated = get_licenser().license_from_manifest(license_data)
|
license_data_validated = get_licenser().license_from_manifest(license_data)
|
||||||
connection.on_commit(lambda: clear_setting_cache.delay(['LICENSE']))
|
connection.on_commit(lambda: clear_setting_cache.delay(['LICENSE']))
|
||||||
except Exception:
|
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)
|
return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
else:
|
else:
|
||||||
license_data_validated = get_licenser().validate()
|
license_data_validated = get_licenser().validate()
|
||||||
@@ -417,7 +417,7 @@ class ApiV2ConfigView(APIView):
|
|||||||
settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host())
|
settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host())
|
||||||
return Response(license_data_validated)
|
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)
|
return Response({"error": _("Invalid subscription")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@extend_schema_if_available(
|
@extend_schema_if_available(
|
||||||
|
|||||||
+3
-3
@@ -27,11 +27,11 @@ class Setting(CreatedModifiedModel):
|
|||||||
json_value = json.dumps(self.value)
|
json_value = json.dumps(self.value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# In the rare case the DB value is invalid JSON.
|
# In the rare case the DB value is invalid JSON.
|
||||||
json_value = u'<Invalid JSON>'
|
json_value = '<Invalid JSON>'
|
||||||
if self.user:
|
if self.user:
|
||||||
return u'{} ({}) = {}'.format(self.key, self.user, json_value)
|
return '{} ({}) = {}'.format(self.key, self.user, json_value)
|
||||||
else:
|
else:
|
||||||
return u'{} = {}'.format(self.key, json_value)
|
return '{} = {}'.format(self.key, json_value)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
encrypted = settings_registry.is_setting_encrypted(self.key)
|
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
|
@pytest.mark.django_db
|
||||||
def test_setting_singleton_detail_retrieve(api_request, dummy_setting):
|
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(
|
with (
|
||||||
'FOO_BAR_2', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'
|
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'}))
|
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -64,8 +65,9 @@ def test_setting_singleton_detail_retrieve(api_request, dummy_setting):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_setting_singleton_detail_invalid_retrieve(api_request, dummy_setting, normal_user):
|
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(
|
with (
|
||||||
'FOO_BAR_2', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'
|
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'}))
|
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'barfoo'}))
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
@@ -104,8 +106,9 @@ def test_setting_singleton_update(api_request, dummy_setting):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_setting_singleton_update_dont_change_readonly_fields(api_request, dummy_setting):
|
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(
|
with (
|
||||||
'awx.conf.views.clear_setting_cache'
|
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})
|
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'}))
|
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
|
@pytest.mark.django_db
|
||||||
def test_setting_singleton_update_dont_change_encrypted_mark(api_request, dummy_setting):
|
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(
|
with (
|
||||||
'awx.conf.views.clear_setting_cache'
|
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'})
|
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$')
|
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):
|
def func_raising_exception(serializer, attrs):
|
||||||
raise serializers.ValidationError('Error')
|
raise serializers.ValidationError('Error')
|
||||||
|
|
||||||
with dummy_setting('FOO_BAR', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'), dummy_validate(
|
with (
|
||||||
'foobar', func_raising_exception
|
dummy_setting('FOO_BAR', field_class=fields.IntegerField, category='FooBar', category_slug='foobar'),
|
||||||
), mock.patch('awx.conf.views.clear_setting_cache'):
|
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})
|
response = api_request('patch', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}), data={'FOO_BAR': 23})
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
@@ -149,8 +155,9 @@ def test_setting_singleton_delete(api_request, dummy_setting):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_setting_singleton_delete_no_read_only_fields(api_request, dummy_setting):
|
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(
|
with (
|
||||||
'awx.conf.views.clear_setting_cache'
|
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'}))
|
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'}))
|
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()
|
counts['pending_jobs'] = models.UnifiedJob.objects.exclude(launch_type='sync').filter(status__in=('pending',)).count()
|
||||||
if connection.vendor == 'postgresql':
|
if connection.vendor == 'postgresql':
|
||||||
with connection.cursor() as cursor:
|
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]
|
counts['database_connections'] = cursor.fetchone()[0]
|
||||||
else:
|
else:
|
||||||
# We should be using postgresql, but if we do that change that ever we should change the below value
|
# 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'''
|
WHERE ({tbl}.{where_column} > '{since.isoformat()}' AND {tbl}.{where_column} <= '{until.isoformat()}')) TO STDOUT WITH CSV HEADER'''
|
||||||
return query
|
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)
|
@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}"'
|
node_label = f'node="{instance}"'
|
||||||
subsystem_label = f',subsystem="{namespace}"' if namespace else ''
|
subsystem_label = f',subsystem="{namespace}"' if namespace else ''
|
||||||
for i, b in enumerate(self.buckets):
|
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}_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}_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'
|
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,
|
read_only=True,
|
||||||
label=_('List of all named url graph nodes.'),
|
label=_('List of all named url graph nodes.'),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Read-only list of key-value pairs that exposes named URL graph topology.'
|
'Read-only list of key-value pairs that exposes named URL graph topology. Use this list to programmatically generate named URLs for resources'
|
||||||
' Use this list to programmatically generate named URLs for resources'
|
|
||||||
),
|
),
|
||||||
category=_('Named URL'),
|
category=_('Named URL'),
|
||||||
category_slug='named-url',
|
category_slug='named-url',
|
||||||
|
|||||||
+2
-4
@@ -488,7 +488,7 @@ register(
|
|||||||
min_value=0,
|
min_value=0,
|
||||||
label=_('Job Event Standard Output Maximum Display Size'),
|
label=_('Job Event Standard Output Maximum Display Size'),
|
||||||
help_text=_(
|
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=_('Jobs'),
|
||||||
category_slug='jobs',
|
category_slug='jobs',
|
||||||
@@ -722,9 +722,7 @@ register(
|
|||||||
default='https',
|
default='https',
|
||||||
label=_('Logging Aggregator Protocol'),
|
label=_('Logging Aggregator Protocol'),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Protocol used to communicate with log aggregator. '
|
'Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in the Logging Aggregator hostname.'
|
||||||
'HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in '
|
|
||||||
'the Logging Aggregator hostname.'
|
|
||||||
),
|
),
|
||||||
category=_('Logging'),
|
category=_('Logging'),
|
||||||
category_slug='logging',
|
category_slug='logging',
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ def signame(sig):
|
|||||||
|
|
||||||
|
|
||||||
class AWXConsumerRedis(object):
|
class AWXConsumerRedis(object):
|
||||||
|
|
||||||
def __init__(self, name, worker):
|
def __init__(self, name, worker):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.pool = WorkerPool()
|
self.pool = WorkerPool()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import connection
|
|||||||
|
|
||||||
|
|
||||||
class AWXTaskWorker(TaskWorker):
|
class AWXTaskWorker(TaskWorker):
|
||||||
|
|
||||||
def on_start(self) -> None:
|
def on_start(self) -> None:
|
||||||
"""Get worker connected so that first task it gets will be worked quickly"""
|
"""Get worker connected so that first task it gets will be worked quickly"""
|
||||||
connection.ensure_connection()
|
connection.ensure_connection()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Command(BaseCommand):
|
|||||||
"""
|
"""
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute(
|
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],
|
[self.INDEX_NAME],
|
||||||
)
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
@@ -52,7 +52,7 @@ class Command(BaseCommand):
|
|||||||
# have set ALTER ROLE ... SET statement_timeout which would kill
|
# have set ALTER ROLE ... SET statement_timeout which would kill
|
||||||
# the index build on large tables.
|
# the index build on large tables.
|
||||||
cursor.execute("SET statement_timeout = 0")
|
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:
|
finally:
|
||||||
connection.connection.autocommit = old_autocommit
|
connection.connection.autocommit = old_autocommit
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ class Command(BaseCommand):
|
|||||||
with tarfile.open(tar_filename, "w") as tar:
|
with tarfile.open(tar_filename, "w") as tar:
|
||||||
for cis in CustomInventoryScript.objects.all():
|
for cis in CustomInventoryScript.objects.all():
|
||||||
# naming convention similar to project paths
|
# naming convention similar to project paths
|
||||||
slug_name = slugify(str(cis.name)).replace(u'-', u'_')
|
slug_name = slugify(str(cis.name)).replace('-', '_')
|
||||||
script_filename = u'_%d__%s' % (int(cis.pk), slug_name)
|
script_filename = '_%d__%s' % (int(cis.pk), slug_name)
|
||||||
script_path = os.path.join(tmpdirname, script_filename)
|
script_path = os.path.join(tmpdirname, script_filename)
|
||||||
|
|
||||||
with open(script_path, 'w') as f:
|
with open(script_path, 'w') as f:
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ class Command(BaseCommand):
|
|||||||
'hosts_added',
|
'hosts_added',
|
||||||
'hosts_deleted',
|
'hosts_deleted',
|
||||||
'indirectly_managed_hosts',
|
'indirectly_managed_hosts',
|
||||||
).order_by(
|
).order_by('date')[offset : offset + limit]
|
||||||
'date'
|
|
||||||
)[offset : offset + limit]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return list_of_queryset
|
return list_of_queryset
|
||||||
|
|||||||
@@ -114,9 +114,15 @@ class Command(BaseCommand):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--hostnames', dest='hostnames', type=str, help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)'
|
'--hostnames', dest='hostnames', type=str, help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)'
|
||||||
)
|
)
|
||||||
|
(
|
||||||
parser.add_argument(
|
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'
|
'--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(
|
parser.add_argument(
|
||||||
'--instance_minimum',
|
'--instance_minimum',
|
||||||
dest='instance_minimum',
|
dest='instance_minimum',
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ def migrate_event_data(apps, schema_editor):
|
|||||||
|
|
||||||
# create parent table
|
# create parent table
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
f'CREATE TABLE {tblname} '
|
f'CREATE TABLE {tblname} (LIKE tmp_{tblname} INCLUDING ALL, job_created TIMESTAMP WITH TIME ZONE NOT NULL) PARTITION BY RANGE(job_created);'
|
||||||
f'(LIKE tmp_{tblname} INCLUDING ALL, job_created TIMESTAMP WITH TIME ZONE NOT NULL) '
|
|
||||||
f'PARTITION BY RANGE(job_created);'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cursor.execute(f'DROP TABLE tmp_{tblname}')
|
cursor.execute(f'DROP TABLE tmp_{tblname}')
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0195_EE_permissions'),
|
('main', '0195_EE_permissions'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0196_indirect_managed_node_audit'),
|
('main', '0196_indirect_managed_node_audit'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [('main', '0197_add_opa_query_path')]
|
dependencies = [('main', '0197_add_opa_query_path')]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import django.db.models.deletion
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0198_alter_inventorysource_source_and_more'),
|
('main', '0198_alter_inventorysource_source_and_more'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ def rename_wfjt(apps, schema_editor):
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0199_inventorygroupvariableswithhistory_and_more'),
|
('main', '0199_inventorygroupvariableswithhistory_and_more'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ logger = logging.getLogger('awx.main.migrations')
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0202_convert_controller_role_definitions'),
|
('main', '0202_convert_controller_role_definitions'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class Migration(migrations.Migration):
|
|||||||
# delete all sso application content permissions
|
# delete all sso application content permissions
|
||||||
# Added reverse_sql=migrations.RunSQL.noop to make this reversible for tests
|
# Added reverse_sql=migrations.RunSQL.noop to make this reversible for tests
|
||||||
migrations.RunSQL(
|
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,
|
reverse_sql=migrations.RunSQL.noop,
|
||||||
),
|
),
|
||||||
# delete sso application content type
|
# delete sso application content type
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0204_squashed_deletions'),
|
('main', '0204_squashed_deletions'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0206_jobhostsummary_host_id_idx'),
|
('main', '0206_jobhostsummary_host_id_idx'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class ActivityStream(models.Model):
|
|||||||
timestamp = self.timestamp
|
timestamp = self.timestamp
|
||||||
else:
|
else:
|
||||||
timestamp = '_delayed_'
|
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):
|
def get_absolute_url(self, request=None):
|
||||||
return reverse('api:activity_stream_detail', kwargs={'pk': self.pk}, request=request)
|
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()
|
self.preferred_instance_groups_cache = self._get_preferred_instance_group_cache()
|
||||||
add_to_update_fields("preferred_instance_groups_cache")
|
add_to_update_fields("preferred_instance_groups_cache")
|
||||||
if not self.name:
|
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")
|
add_to_update_fields("name")
|
||||||
if self.task_impact == 0:
|
if self.task_impact == 0:
|
||||||
self.task_impact = self._get_task_impact()
|
self.task_impact = self._get_task_impact()
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ class BaseModel(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if 'name' in self.__dict__:
|
if 'name' in self.__dict__:
|
||||||
return u'%s-%s' % (self.name, self.pk)
|
return '%s-%s' % (self.name, self.pk)
|
||||||
else:
|
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):
|
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)
|
return reverse('api:job_event_detail', kwargs={'pk': self.pk}, request=request)
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
def _hostnames(self):
|
||||||
hostnames = set()
|
hostnames = set()
|
||||||
@@ -717,7 +717,7 @@ class BaseCommandEvent(CreatedModifiedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
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
|
@classmethod
|
||||||
def create_from_data(cls, **kwargs):
|
def create_from_data(cls, **kwargs):
|
||||||
|
|||||||
@@ -701,7 +701,7 @@ class WebhookMixin(models.Model):
|
|||||||
if response.status_code < 400:
|
if response.status_code < 400:
|
||||||
logger.debug("Webhook status update sent.")
|
logger.debug("Webhook status update sent.")
|
||||||
else:
|
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):
|
class OpaQueryPathMixin(models.Model):
|
||||||
|
|||||||
@@ -371,8 +371,8 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEn
|
|||||||
self._skip_update = bool(kwargs.pop('skip_update', False))
|
self._skip_update = bool(kwargs.pop('skip_update', False))
|
||||||
# Create auto-generated local path if project uses SCM.
|
# Create auto-generated local path if project uses SCM.
|
||||||
if self.pk and self.scm_type and not self.local_path.startswith('_'):
|
if self.pk and self.scm_type and not self.local_path.startswith('_'):
|
||||||
slug_name = slugify(str(self.name)).replace(u'-', u'_')
|
slug_name = slugify(str(self.name)).replace('-', '_')
|
||||||
self.local_path = u'_%d__%s' % (int(self.pk), slug_name)
|
self.local_path = '_%d__%s' % (int(self.pk), slug_name)
|
||||||
if 'local_path' not in update_fields:
|
if 'local_path' not in update_fields:
|
||||||
update_fields.append('local_path')
|
update_fields.append('local_path')
|
||||||
# Do the actual save.
|
# Do the actual save.
|
||||||
|
|||||||
+12
-6
@@ -180,9 +180,9 @@ class Role(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if 'role_field' in self.__dict__:
|
if 'role_field' in self.__dict__:
|
||||||
return u'%s-%s' % (self.name, self.pk)
|
return '%s-%s' % (self.name, self.pk)
|
||||||
else:
|
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):
|
def save(self, *args, **kwargs):
|
||||||
super(Role, self).save(*args, **kwargs)
|
super(Role, self).save(*args, **kwargs)
|
||||||
@@ -365,7 +365,8 @@ class Role(models.Model):
|
|||||||
if len(removals) > 0:
|
if len(removals) > 0:
|
||||||
for ids in split_ids_for_sqlite(removals):
|
for ids in split_ids_for_sqlite(removals):
|
||||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||||
cursor.execute('''
|
cursor.execute(
|
||||||
|
'''
|
||||||
DELETE FROM %(ancestors_table)s
|
DELETE FROM %(ancestors_table)s
|
||||||
WHERE descendent_id IN (%(ids)s)
|
WHERE descendent_id IN (%(ids)s)
|
||||||
AND descendent_id != ancestor_id
|
AND descendent_id != ancestor_id
|
||||||
@@ -377,7 +378,9 @@ class Role(models.Model):
|
|||||||
WHERE parents.from_role_id = %(ancestors_table)s.descendent_id
|
WHERE parents.from_role_id = %(ancestors_table)s.descendent_id
|
||||||
AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id
|
AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id
|
||||||
)
|
)
|
||||||
''' % sql_params)
|
'''
|
||||||
|
% sql_params
|
||||||
|
)
|
||||||
|
|
||||||
delete_ct += cursor.rowcount
|
delete_ct += cursor.rowcount
|
||||||
|
|
||||||
@@ -385,7 +388,8 @@ class Role(models.Model):
|
|||||||
if len(additions) > 0:
|
if len(additions) > 0:
|
||||||
for ids in split_ids_for_sqlite(additions):
|
for ids in split_ids_for_sqlite(additions):
|
||||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
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)
|
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 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,
|
SELECT roles.id from_id,
|
||||||
@@ -415,7 +419,9 @@ class Role(models.Model):
|
|||||||
AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id
|
AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id
|
||||||
)
|
)
|
||||||
|
|
||||||
''' % sql_params)
|
'''
|
||||||
|
% sql_params
|
||||||
|
)
|
||||||
insert_ct += cursor.rowcount
|
insert_ct += cursor.rowcount
|
||||||
|
|
||||||
if insert_ct == 0 and delete_ct == 0:
|
if insert_ct == 0 and delete_ct == 0:
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
|
|||||||
return rruleset
|
return rruleset
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
def get_absolute_url(self, request=None):
|
||||||
return reverse('api:schedule_detail', kwargs={'pk': self.pk}, request=request)
|
return reverse('api:schedule_detail', kwargs={'pk': self.pk}, request=request)
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ class UnifiedJob(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def __str__(self):
|
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
|
@property
|
||||||
def log_format(self):
|
def log_format(self):
|
||||||
@@ -1416,14 +1416,14 @@ class UnifiedJob(
|
|||||||
|
|
||||||
if not all(opts.values()):
|
if not all(opts.values()):
|
||||||
missing_fields = ', '.join([k for k, v in opts.items() if not v])
|
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'])
|
self.save(update_fields=['job_explanation'])
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
def pre_start(self):
|
def pre_start(self):
|
||||||
if not self.can_start:
|
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'])
|
self.save(update_fields=['job_explanation'])
|
||||||
return (False, None)
|
return (False, None)
|
||||||
|
|
||||||
|
|||||||
@@ -256,8 +256,7 @@ class WorkflowManager(TaskBase):
|
|||||||
)
|
)
|
||||||
display_list = [spawn_node.unified_job_template] + workflow_ancestors
|
display_list = [spawn_node.unified_job_template] + workflow_ancestors
|
||||||
job.job_explanation = gettext_noop(
|
job.job_explanation = gettext_noop(
|
||||||
"Workflow Job spawned from workflow could not start because it "
|
"Workflow Job spawned from workflow could not start because it would result in recursion (spawn order, most recent first: {})"
|
||||||
"would result in recursion (spawn order, most recent first: {})"
|
|
||||||
).format(', '.join('<{}>'.format(tmp) for tmp in display_list))
|
).format(', '.join('<{}>'.format(tmp) for tmp in display_list))
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -344,7 +343,7 @@ class DependencyManager(TaskBase):
|
|||||||
if update.status in ['waiting', 'pending', 'running']:
|
if update.status in ['waiting', 'pending', 'running']:
|
||||||
return False
|
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):
|
def get_or_create_project_update(self, project_id):
|
||||||
project = self.all_projects.get(project_id, None)
|
project = self.all_projects.get(project_id, None)
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ class BaseTask(object):
|
|||||||
project.save()
|
project.save()
|
||||||
lock_path = project.get_lock_file()
|
lock_path = project.get_lock_file()
|
||||||
if lock_path is None:
|
if lock_path is None:
|
||||||
raise RuntimeError(u'Invalid lock file path')
|
raise RuntimeError('Invalid lock file path')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT)
|
self.lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ from awx.main.utils.reload import stop_local_services
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.tasks.system')
|
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 \
|
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. \
|
isn't supported by the installed version of OpenSSH on this instance. \
|
||||||
Try upgrading OpenSSH or providing your private key in an different format. \
|
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 = []
|
active_task_ids = []
|
||||||
try:
|
try:
|
||||||
|
|
||||||
logger.debug("Querying dispatcherd API for running tasks")
|
logger.debug("Querying dispatcherd API for running tasks")
|
||||||
data = binder.control('running')
|
data = binder.control('running')
|
||||||
|
|
||||||
@@ -934,8 +933,10 @@ def awx_periodic_scheduler():
|
|||||||
continue
|
continue
|
||||||
if not can_start:
|
if not can_start:
|
||||||
new_unified_job.status = 'failed'
|
new_unified_job.status = 'failed'
|
||||||
new_unified_job.job_explanation = gettext_noop("Scheduled job could not start because it \
|
new_unified_job.job_explanation = gettext_noop(
|
||||||
was not in the right state or required manual credentials")
|
"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.save(update_fields=['status', 'job_explanation'])
|
||||||
new_unified_job.websocket_emit_status("failed")
|
new_unified_job.websocket_emit_status("failed")
|
||||||
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
|
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ from django import template
|
|||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
CONSONANT_SOUND = re.compile(r'''one(![ir])''', re.IGNORECASE | re.VERBOSE) # noqa
|
CONSONANT_SOUND = re.compile(r'''one(![ir])''', re.IGNORECASE | re.VERBOSE) # noqa
|
||||||
VOWEL_SOUND = re.compile(
|
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
|
||||||
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
|
@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 index, relationship in zip([7, 8, 9], ["success_nodes", "failure_nodes", "always_nodes"]):
|
||||||
for i, l in enumerate(lines):
|
for i, l in enumerate(lines):
|
||||||
related_nodes = [int(e) for e in l[index].split(",")] if l[index] else []
|
related_nodes = [int(e) for e in l[index].split(",")] if l[index] else []
|
||||||
assert related_nodes == list(
|
assert related_nodes == list(getattr(workflow_job.workflow_nodes.all()[i], relationship).all().values_list("id", flat=True)), (
|
||||||
getattr(workflow_job.workflow_nodes.all()[i], relationship).all().values_list("id", flat=True)
|
f"(right side) workflow_nodes.all()[{i}].{relationship}.all()"
|
||||||
), f"(right side) workflow_nodes.all()[{i}].{relationship}.all()"
|
)
|
||||||
|
|||||||
@@ -233,9 +233,10 @@ class TestAnalyticsGenericView:
|
|||||||
request = RequestFactory().post('/some/path')
|
request = RequestFactory().post('/some/path')
|
||||||
view = AnalyticsGenericView()
|
view = AnalyticsGenericView()
|
||||||
|
|
||||||
with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client, mock.patch(
|
with (
|
||||||
'awx.api.views.analytics.AnalyticsGenericView._base_auth_request'
|
mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client,
|
||||||
) as mock_base_auth_request:
|
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
|
# Configure the mock OIDCClient instance and its make_request method
|
||||||
mock_client_instance = mock.Mock()
|
mock_client_instance = mock.Mock()
|
||||||
mock_oidc_client.return_value = mock_client_instance
|
mock_oidc_client.return_value = mock_client_instance
|
||||||
|
|||||||
@@ -930,7 +930,7 @@ def test_field_removal(put, organization, admin, credentialtype_ssh):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cred = 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()
|
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):
|
def test_credential_type_mutability(patch, organization, admin, credentialtype_ssh, credentialtype_aws, relation, related_obj):
|
||||||
cred = Credential(
|
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()
|
cred.save()
|
||||||
|
|
||||||
@@ -967,7 +967,7 @@ def test_credential_type_mutability(patch, organization, admin, credentialtype_s
|
|||||||
def _change_credential_type():
|
def _change_credential_type():
|
||||||
return patch(
|
return patch(
|
||||||
reverse('api:credential_detail', kwargs={'pk': cred.pk}),
|
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,
|
admin,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -992,7 +992,7 @@ def test_vault_credential_type_mutability(patch, organization, admin, credential
|
|||||||
name='Best credential ever',
|
name='Best credential ever',
|
||||||
organization=organization,
|
organization=organization,
|
||||||
inputs={
|
inputs={
|
||||||
'vault_password': u'some-vault',
|
'vault_password': 'some-vault',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cred.save()
|
cred.save()
|
||||||
@@ -1004,7 +1004,7 @@ def test_vault_credential_type_mutability(patch, organization, admin, credential
|
|||||||
def _change_credential_type():
|
def _change_credential_type():
|
||||||
return patch(
|
return patch(
|
||||||
reverse('api:credential_detail', kwargs={'pk': cred.pk}),
|
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,
|
admin,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1025,7 +1025,7 @@ def test_vault_credential_type_mutability(patch, organization, admin, credential
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cloud_credential_type_mutability(patch, organization, admin, credentialtype_ssh, credentialtype_aws):
|
def test_cloud_credential_type_mutability(patch, organization, admin, credentialtype_ssh, credentialtype_aws):
|
||||||
cred = Credential(
|
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()
|
cred.save()
|
||||||
|
|
||||||
@@ -1036,7 +1036,7 @@ def test_cloud_credential_type_mutability(patch, organization, admin, credential
|
|||||||
def _change_credential_type():
|
def _change_credential_type():
|
||||||
return patch(
|
return patch(
|
||||||
reverse('api:credential_detail', kwargs={'pk': cred.pk}),
|
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,
|
admin,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1084,7 +1084,7 @@ def test_ssh_unlock_needed(put, organization, admin, credentialtype_ssh):
|
|||||||
credential_type=credentialtype_ssh,
|
credential_type=credentialtype_ssh,
|
||||||
name='Best credential ever',
|
name='Best credential ever',
|
||||||
organization=organization,
|
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()
|
cred.save()
|
||||||
|
|
||||||
@@ -1110,7 +1110,7 @@ def test_ssh_unlock_not_needed(put, organization, admin, credentialtype_ssh):
|
|||||||
name='Best credential ever',
|
name='Best credential ever',
|
||||||
organization=organization,
|
organization=organization,
|
||||||
inputs={
|
inputs={
|
||||||
'username': u'joe',
|
'username': 'joe',
|
||||||
'ssh_key_data': EXAMPLE_PRIVATE_KEY,
|
'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,
|
credential_type=credentialtype_ssh,
|
||||||
name='Best credential ever',
|
name='Best credential ever',
|
||||||
organization=organization,
|
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()
|
cred.save()
|
||||||
|
|
||||||
@@ -1165,7 +1165,7 @@ def test_ssh_bad_key_unlock_not_checked(put, organization, admin, credentialtype
|
|||||||
name='Best credential ever',
|
name='Best credential ever',
|
||||||
organization=organization,
|
organization=organization,
|
||||||
inputs={
|
inputs={
|
||||||
'username': u'oscar',
|
'username': 'oscar',
|
||||||
'ssh_key_data': 'invalid-key',
|
'ssh_key_data': 'invalid-key',
|
||||||
'ssh_key_unlock': 'unchecked-unlock',
|
'ssh_key_unlock': 'unchecked-unlock',
|
||||||
},
|
},
|
||||||
@@ -1251,7 +1251,7 @@ def test_secret_encryption_previous_value(patch, organization, admin, credential
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cred = 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()
|
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)
|
response_sorted = sorted(response.data['active_jobs'], key=sort_keys)
|
||||||
expect_sorted = sorted(expect_transformed, 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
|
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):
|
def test_host_filter_unicode(post, admin_user, organization):
|
||||||
post(
|
post(
|
||||||
reverse('api:inventory_list'),
|
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,
|
user=admin_user,
|
||||||
expect=201,
|
expect=201,
|
||||||
)
|
)
|
||||||
si = Inventory.objects.get(name='smart inventory')
|
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
|
@pytest.mark.django_db
|
||||||
@@ -302,7 +302,7 @@ def test_host_filter_invalid_ansible_facts_lookup(post, admin_user, organization
|
|||||||
'name': 'smart inventory',
|
'name': 'smart inventory',
|
||||||
'kind': 'smart',
|
'kind': 'smart',
|
||||||
'organization': organization.pk,
|
'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,
|
user=admin_user,
|
||||||
expect=400,
|
expect=400,
|
||||||
@@ -417,7 +417,7 @@ def test_inventory_source_vars_prohibition(post, inventory, admin_user):
|
|||||||
mock_settings.INV_ENV_VARIABLE_BLOCKED = ('FOOBAR',)
|
mock_settings.INV_ENV_VARIABLE_BLOCKED = ('FOOBAR',)
|
||||||
r = post(
|
r = post(
|
||||||
reverse('api:inventory_source_list'),
|
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,
|
admin_user,
|
||||||
expect=400,
|
expect=400,
|
||||||
)
|
)
|
||||||
@@ -729,7 +729,6 @@ class TestConstructedInventory:
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestInventoryAllVariables:
|
class TestInventoryAllVariables:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def simulate_update_from_source(inv_src, variables_dict, overwrite_vars=True):
|
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,
|
expect=400,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.data['job_type'] == [u'"foobicate" is not a valid choice.']
|
assert response.data['job_type'] == ['"foobicate" is not a valid choice.']
|
||||||
assert response.data['inventory'] == [u'Invalid pk "87865" - object does not exist.']
|
assert response.data['inventory'] == ['Invalid pk "87865" - object does not exist.']
|
||||||
assert response.data['credentials'] == [u'Invalid pk "48474" - object does not exist.']
|
assert response.data['credentials'] == ['Invalid pk "48474" - object does not exist.']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@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
|
# 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)
|
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
|
@pytest.mark.django_db
|
||||||
@@ -540,15 +540,15 @@ def test_job_launch_fails_with_missing_multivault_password(machine_credential, v
|
|||||||
assert {
|
assert {
|
||||||
'credential_type': vault_cred_first.credential_type_id,
|
'credential_type': vault_cred_first.credential_type_id,
|
||||||
'passwords_needed': ['vault_password.abc'],
|
'passwords_needed': ['vault_password.abc'],
|
||||||
'vault_id': u'abc',
|
'vault_id': 'abc',
|
||||||
'name': u'Vault #1',
|
'name': 'Vault #1',
|
||||||
'id': vault_cred_first.id,
|
'id': vault_cred_first.id,
|
||||||
} in resp.data['defaults']['credentials']
|
} in resp.data['defaults']['credentials']
|
||||||
assert {
|
assert {
|
||||||
'credential_type': vault_cred_second.credential_type_id,
|
'credential_type': vault_cred_second.credential_type_id,
|
||||||
'passwords_needed': ['vault_password.xyz'],
|
'passwords_needed': ['vault_password.xyz'],
|
||||||
'vault_id': u'xyz',
|
'vault_id': 'xyz',
|
||||||
'name': u'Vault #2',
|
'name': 'Vault #2',
|
||||||
'id': vault_cred_second.id,
|
'id': vault_cred_second.id,
|
||||||
} in resp.data['defaults']['credentials']
|
} in resp.data['defaults']['credentials']
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ class TestLicenseCacheClearing:
|
|||||||
"""Test that posting a manifest to /api/v2/config/ clears the LICENSE cache"""
|
"""Test that posting a manifest to /api/v2/config/ clears the LICENSE cache"""
|
||||||
|
|
||||||
# Mock the licenser and clear_setting_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(
|
with (
|
||||||
'awx.api.views.root.clear_setting_cache'
|
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
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
|
# Set up mock license data
|
||||||
mock_license_data = {'valid_key': True, 'license_type': 'enterprise', 'instance_count': 100, 'subscription_name': 'Test Enterprise License'}
|
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"""
|
"""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:
|
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
|
# Make the DELETE request
|
||||||
url = reverse('api:api_v2_config_view')
|
url = reverse('api:api_v2_config_view')
|
||||||
delete(url, admin_user, expect=204)
|
delete(url, admin_user, expect=204)
|
||||||
@@ -72,10 +73,12 @@ class TestLicenseCacheClearing:
|
|||||||
def test_attach_view_clears_cache(self, admin_user, post):
|
def test_attach_view_clears_cache(self, admin_user, post):
|
||||||
"""Test that posting to /api/v2/config/attach/ clears the LICENSE cache"""
|
"""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(
|
with (
|
||||||
'django.db.connection.on_commit'
|
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||||
) as mock_on_commit, patch('awx.api.views.root.settings') as mock_settings:
|
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
|
# Set up subscription credentials in settings
|
||||||
mock_settings.SUBSCRIPTIONS_CLIENT_ID = 'test-client-id'
|
mock_settings.SUBSCRIPTIONS_CLIENT_ID = 'test-client-id'
|
||||||
mock_settings.SUBSCRIPTIONS_CLIENT_SECRET = 'test-client-secret'
|
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):
|
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"""
|
"""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(
|
with (
|
||||||
'django.db.connection.on_commit'
|
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||||
) as mock_on_commit:
|
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
|
# Set up mock licenser with different subscription
|
||||||
mock_licenser = MagicMock()
|
mock_licenser = MagicMock()
|
||||||
subscription_data = {'subscription_id': 'different-subscription-456', 'valid_key': False, 'license_type': 'enterprise'} # Different ID
|
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):
|
def test_manifest_validation_error_no_cache_clear(self, admin_user, post):
|
||||||
"""Test that config view doesn't clear cache when manifest validation fails"""
|
"""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(
|
with (
|
||||||
'awx.api.views.root.clear_setting_cache'
|
patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate,
|
||||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
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 validation to raise ValueError
|
||||||
mock_validate.side_effect = ValueError("Invalid manifest")
|
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):
|
def test_license_processing_error_no_cache_clear(self, admin_user, post):
|
||||||
"""Test that config view doesn't clear cache when license processing fails"""
|
"""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(
|
with (
|
||||||
'awx.api.views.root.clear_setting_cache'
|
patch('awx.api.views.root.get_licenser') as mock_get_licenser,
|
||||||
) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
|
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 validation to succeed but license processing to fail
|
||||||
mock_validate.return_value = [{'some': 'manifest_data'}]
|
mock_validate.return_value = [{'some': 'manifest_data'}]
|
||||||
mock_licenser = MagicMock()
|
mock_licenser = MagicMock()
|
||||||
|
|||||||
@@ -62,10 +62,11 @@ def oidc_credential(oidc_credentialtype):
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_oidc_backend():
|
def mock_oidc_backend():
|
||||||
"""Fixture that mocks OIDC JWT generation and credential 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(
|
with (
|
||||||
'awx.main.models.credential.CredentialType.plugin', new_callable=mock.PropertyMock
|
mock.patch('awx.api.views.retrieve_workload_identity_jwt_with_claims') as mock_jwt,
|
||||||
) as mock_plugin:
|
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
|
# Set default return values
|
||||||
mock_jwt.return_value = 'fake.jwt.token'
|
mock_jwt.return_value = 'fake.jwt.token'
|
||||||
mock_decode.return_value = {'iss': 'http://gateway/o', 'aud': 'vault'}
|
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)
|
resp_sorted = sorted(resp.data['active_jobs'], key=sort_keys)
|
||||||
expect_sorted = sorted(expect_transformed, 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
|
assert resp_sorted == expect_sorted
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -150,13 +150,13 @@ def test_host_filter_unicode(post, admin_user, smart_inv_org):
|
|||||||
'name': 'unicode-smart',
|
'name': 'unicode-smart',
|
||||||
'kind': 'smart',
|
'kind': 'smart',
|
||||||
'organization': smart_inv_org.pk,
|
'organization': smart_inv_org.pk,
|
||||||
'host_filter': u'ansible_facts__ansible_distribution=レッドハット',
|
'host_filter': 'ansible_facts__ansible_distribution=レッドハット',
|
||||||
},
|
},
|
||||||
user=admin_user,
|
user=admin_user,
|
||||||
expect=201,
|
expect=201,
|
||||||
)
|
)
|
||||||
si = Inventory.objects.get(name='unicode-smart')
|
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
|
@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',
|
'name': 'invalid-lookup-smart',
|
||||||
'kind': 'smart',
|
'kind': 'smart',
|
||||||
'organization': smart_inv_org.pk,
|
'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,
|
user=admin_user,
|
||||||
expect=400,
|
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})
|
url = reverse('api:system_job_detail', kwargs={'pk': job.pk})
|
||||||
response = get(url, user=admin, expect=200)
|
response = get(url, user=admin, expect=200)
|
||||||
assert response.data['result_stdout'] == (
|
assert response.data['result_stdout'] == (
|
||||||
'Standard Output too large to display ({actual} bytes), only download '
|
'Standard Output too large to display ({actual} bytes), only download supported for sizes over {max} bytes.'.format(
|
||||||
'supported for sizes over {max} bytes.'.format(actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY)
|
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)
|
response = get(url + '?format={}'.format(fmt), user=admin, expect=200)
|
||||||
assert smart_str(response.content) == (
|
assert smart_str(response.content) == (
|
||||||
'Standard Output too large to display ({actual} bytes), only download '
|
'Standard Output too large to display ({actual} bytes), only download supported for sizes over {max} bytes.'.format(
|
||||||
'supported for sizes over {max} bytes.'.format(actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY)
|
actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = get(url + '?format={}_download'.format(fmt), user=admin, expect=200)
|
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)
|
response = get(url + '?format={}'.format(fmt), user=admin, expect=200)
|
||||||
assert smart_str(response.content) == (
|
assert smart_str(response.content) == (
|
||||||
'Standard Output too large to display ({actual} bytes), only download '
|
'Standard Output too large to display ({actual} bytes), only download supported for sizes over {max} bytes.'.format(
|
||||||
'supported for sizes over {max} bytes.'.format(actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY)
|
actual=total_bytes, max=settings.STDOUT_MAX_BYTES_DISPLAY
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = get(url + '?format={}'.format(fmt + '_download'), user=admin, expect=200)
|
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 = Parent()
|
||||||
job.save()
|
job.save()
|
||||||
for i in range(3):
|
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
|
url = reverse(view, kwargs={'pk': job.pk}) + '?format=' + fmt
|
||||||
|
|
||||||
response = get(url, user=admin, expect=200)
|
response = get(url, user=admin, expect=200)
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class TestKeyRegeneration:
|
|||||||
name='Example Template',
|
name='Example Template',
|
||||||
survey_spec=survey_spec_factory([{'variable': 'secret_key', 'default': encrypt_value('donttell', pk=None), 'type': 'password'}]),
|
survey_spec=survey_spec_factory([{'variable': 'secret_key', 'default': encrypt_value('donttell', pk=None), 'type': 'password'}]),
|
||||||
survey_enabled=True,
|
survey_enabled=True,
|
||||||
**params
|
**params,
|
||||||
)
|
)
|
||||||
job = jt.create_unified_job()
|
job = jt.create_unified_job()
|
||||||
assert jt.survey_spec['spec'][0]['default'].startswith(PREFIX)
|
assert jt.survey_spec['spec'][0]['default'].startswith(PREFIX)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from awx.main.models import Organization, Team
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestClaimsOldRbacSync:
|
class TestClaimsOldRbacSync:
|
||||||
|
|
||||||
def _build_claims(self, orgs, teams):
|
def _build_claims(self, orgs, teams):
|
||||||
"""Build a claims dict from org/team model instances."""
|
"""Build a claims dict from org/team model instances."""
|
||||||
objects = {"organization": [], "team": []}
|
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))
|
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
|
# Verify team-to-team relationships are removed after consolidation
|
||||||
assert not RoleTeamAssignment.objects.filter(
|
assert not RoleTeamAssignment.objects.filter(role_definition=team_member_role, team=team_f, object_id=team_e.id).exists(), (
|
||||||
role_definition=team_member_role, team=team_f, object_id=team_e.id
|
"Team-to-team relationship F→E should be removed"
|
||||||
).exists(), "Team-to-team relationship F→E should be removed"
|
)
|
||||||
assert not RoleTeamAssignment.objects.filter(
|
assert not RoleTeamAssignment.objects.filter(role_definition=team_member_role, team=team_g, object_id=team_f.id).exists(), (
|
||||||
role_definition=team_member_role, team=team_g, object_id=team_f.id
|
"Team-to-team relationship G→F should be removed"
|
||||||
).exists(), "Team-to-team relationship G→F should be removed"
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class TestHostManagerDeferral:
|
|||||||
"""ansible_facts should be in the deferred set for default Host queries."""
|
"""ansible_facts should be in the deferred set for default Host queries."""
|
||||||
qs = Host.objects.all()
|
qs = Host.objects.all()
|
||||||
deferred = qs.query.deferred_loading[0]
|
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):
|
def test_ansible_facts_accessible_when_needed(self, inventory):
|
||||||
"""Deferred fields are still accessible — Django fetches on access."""
|
"""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 = {}
|
rd_data = {}
|
||||||
for rd in model_rds:
|
for rd in model_rds:
|
||||||
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
||||||
assert (
|
assert 'Compat' not in rd.name, (
|
||||||
'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)}'
|
||||||
), 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)
|
assert new_codenames == set(old_codenames)
|
||||||
|
|
||||||
# In the old system these models did not have object-level roles, all others expect some model roles
|
# 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)
|
response = get(reverse('api:unified_job_list'), team_user)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
result_ids = [r['id'] for r in response.data['results']]
|
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
|
@pytest.mark.django_db
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class TestFinishFactCacheConcurrentProtection:
|
|||||||
)
|
)
|
||||||
|
|
||||||
host.refresh_from_db()
|
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
|
@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
|
# Assert inventory plugin inventory file is in private_data_dir
|
||||||
inventory_filename = InventorySource.injectors[inventory_update.source]().filename
|
inventory_filename = InventorySource.injectors[inventory_update.source]().filename
|
||||||
assert (
|
assert len([True for k in content.keys() if k.endswith(inventory_filename)]) > 0, (
|
||||||
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()}"
|
||||||
), f"'{inventory_filename}' file not found in inventory update runtime files {content.keys()}"
|
)
|
||||||
|
|
||||||
env.pop('ANSIBLE_COLLECTIONS_PATH', None)
|
env.pop('ANSIBLE_COLLECTIONS_PATH', None)
|
||||||
base_dir = os.path.join(DATA, 'plugins')
|
base_dir = os.path.join(DATA, 'plugins')
|
||||||
|
|||||||
@@ -164,15 +164,15 @@ class TestMigrationSmoke:
|
|||||||
|
|
||||||
CredentialType = new_state.apps.get_model('main', 'CredentialType')
|
CredentialType = new_state.apps.get_model('main', 'CredentialType')
|
||||||
for expected_name in validate_exists:
|
for expected_name in validate_exists:
|
||||||
assert CredentialType.objects.filter(
|
assert CredentialType.objects.filter(name=expected_name).exists(), (
|
||||||
name=expected_name
|
f'Could not find {expected_name} credential type name, all names: {list(CredentialType.objects.values_list("name", flat=True))}'
|
||||||
).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
|
# Verify the system_administrator role exists
|
||||||
Role = new_state.apps.get_model('main', 'Role')
|
Role = new_state.apps.get_model('main', 'Role')
|
||||||
assert Role.objects.filter(
|
assert Role.objects.filter(singleton_name='system_administrator', role_field='system_administrator').exists(), (
|
||||||
singleton_name='system_administrator', role_field='system_administrator'
|
"expected to find a system_administrator singleton role"
|
||||||
).exists(), "expected to find a system_administrator singleton role"
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@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
|
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.
|
# Assertion 1: The CredentialType with the old 'github_app' kind should no longer exist.
|
||||||
assert not CredentialType.objects.filter(
|
assert not CredentialType.objects.filter(namespace='github_app').exists(), (
|
||||||
namespace='github_app'
|
"CredentialType with old 'github_app' kind should no longer exist after migration."
|
||||||
).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
|
# Assertion 2: The CredentialType should now exist with the new 'github_app_lookup' kind
|
||||||
# and retain its original name.
|
# and retain its original name.
|
||||||
assert CredentialType.objects.filter(
|
assert CredentialType.objects.filter(namespace='github_app_lookup', name='Legacy GitHub App Credential').exists(), (
|
||||||
namespace='github_app_lookup', name='Legacy GitHub App Credential'
|
"CredentialType should be updated to 'github_app_lookup' and retain its name."
|
||||||
).exists(), "CredentialType should be updated to 'github_app_lookup' and retain its name."
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -245,9 +245,9 @@ class TestSystemAuditorMigration:
|
|||||||
new_state = migrator.apply_tested_migration(('main', '0192_custom_roles'))
|
new_state = migrator.apply_tested_migration(('main', '0192_custom_roles'))
|
||||||
RoleUserAssignment = new_state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
RoleUserAssignment = new_state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||||
|
|
||||||
assert RoleUserAssignment.objects.filter(
|
assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').exists(), (
|
||||||
user=user.id, role_definition__name='Platform Auditor'
|
"system_auditor member should have been granted Platform Auditor"
|
||||||
).exists(), "system_auditor member should have been granted Platform Auditor"
|
)
|
||||||
|
|
||||||
def test_fix_system_auditor_migration_backfills_missing(self, migrator):
|
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
|
# Simulate the pre-fix state: remove the Platform Auditor assignment
|
||||||
platform_auditor = RoleDefinition.objects.get(name='Platform Auditor')
|
platform_auditor = RoleDefinition.objects.get(name='Platform Auditor')
|
||||||
RoleUserAssignment.objects.filter(user=user.id, role_definition=platform_auditor).delete()
|
RoleUserAssignment.objects.filter(user=user.id, role_definition=platform_auditor).delete()
|
||||||
assert not RoleUserAssignment.objects.filter(
|
assert not RoleUserAssignment.objects.filter(user=user.id, role_definition=platform_auditor).exists(), (
|
||||||
user=user.id, role_definition=platform_auditor
|
"Setup: user should not have Platform Auditor before corrective migration"
|
||||||
).exists(), "Setup: user should not have Platform Auditor before corrective migration"
|
)
|
||||||
|
|
||||||
state = migrator.apply_tested_migration(('main', '0208_fix_system_auditor_migration'))
|
state = migrator.apply_tested_migration(('main', '0208_fix_system_auditor_migration'))
|
||||||
RoleUserAssignment = state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
RoleUserAssignment = state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||||
|
|
||||||
assert RoleUserAssignment.objects.filter(
|
assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').exists(), (
|
||||||
user=user.id, role_definition__name='Platform Auditor'
|
"Corrective migration should have backfilled Platform Auditor"
|
||||||
).exists(), "Corrective migration should have backfilled Platform Auditor"
|
)
|
||||||
|
|
||||||
def test_fix_system_auditor_migration_no_duplicates(self, migrator):
|
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'))
|
state = migrator.apply_tested_migration(('main', '0208_fix_system_auditor_migration'))
|
||||||
RoleUserAssignment = state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
RoleUserAssignment = state.apps.get_model('dab_rbac', 'RoleUserAssignment')
|
||||||
|
|
||||||
assert (
|
assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').count() == 1, (
|
||||||
RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Platform Auditor').count() == 1
|
"Should have exactly one Platform Auditor assignment, not duplicates"
|
||||||
), "Should have exactly one Platform Auditor assignment, not duplicates"
|
)
|
||||||
|
|||||||
@@ -163,9 +163,11 @@ def test_custom_environment_injection(post, user, organization):
|
|||||||
expect=201,
|
expect=201,
|
||||||
)
|
)
|
||||||
template = NotificationTemplate.objects.get(pk=response.data['id'])
|
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(
|
with (
|
||||||
HTTPAdapter, 'send'
|
pytest.raises(ConnectionError),
|
||||||
) as fake_send:
|
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):
|
def _send_side_effect(request, **kw):
|
||||||
assert select_proxy(request.url, kw['proxies']) == '192.168.50.100:1234'
|
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)
|
# Verify the embedded query was used (includes device_type in facts)
|
||||||
host_audit = IndirectManagedNodeAudit.objects.filter(job=job).first()
|
host_audit = IndirectManagedNodeAudit.objects.filter(job=job).first()
|
||||||
assert host_audit.facts == {'device_type': 'Fake Host'}, (
|
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
|
# Critical safety check: fallback must never count MORE than exact
|
||||||
assert fallback_count <= exact_count, (
|
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
|
# 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
|
# No audit records should exist for this job
|
||||||
assert IndirectManagedNodeAudit.objects.filter(job=job).count() == 0, (
|
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)
|
reader_extra_vars = json.loads(reader_node.job.extra_vars)
|
||||||
|
|
||||||
# var1: only set by outer job_first, no conflict — should propagate through
|
# 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"
|
# var2: set by outer as "should-be-overridden", then by inner as "from-inner"
|
||||||
# Inner workflow's own ancestor artifacts should take precedence
|
# 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
|
# 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)
|
@pytest.mark.django_db(transaction=True)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class TestJobSerializerGetRelated:
|
|||||||
class TestJobSerializerSubstitution:
|
class TestJobSerializerSubstitution:
|
||||||
def test_survey_password_hide(self, mocker):
|
def test_survey_password_hide(self, mocker):
|
||||||
job = mocker.MagicMock(
|
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)
|
serializer = JobSerializer(job)
|
||||||
rep = serializer.to_representation(job)
|
rep = serializer.to_representation(job)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class TestNotificationTemplateSerializer:
|
|||||||
},
|
},
|
||||||
{'started': {'body': '{{ job_metadata }}'}},
|
{'started': {'body': '{{ job_metadata }}'}},
|
||||||
{'started': {'body': '{{ job.summary_fields.inventory.total_hosts }}'}},
|
{'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):
|
def test_valid_messages(self, valid_messages):
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ def test_unified_job_list_field_consistency():
|
|||||||
for cls in UnifiedJob.__subclasses__():
|
for cls in UnifiedJob.__subclasses__():
|
||||||
list_serializer = getattr(serializers, '{}ListSerializer'.format(cls.__name__))
|
list_serializer = getattr(serializers, '{}ListSerializer'.format(cls.__name__))
|
||||||
unified_serializer = serializers.UnifiedJobListSerializer().get_sub_serializer(cls())
|
unified_serializer = serializers.UnifiedJobListSerializer().get_sub_serializer(cls())
|
||||||
assert set(list_serializer().fields.keys()) == set(
|
assert set(list_serializer().fields.keys()) == set(unified_serializer().fields.keys()), (
|
||||||
unified_serializer().fields.keys()
|
'Mismatch between {} list serializer & unified list serializer'.format(cls)
|
||||||
), 'Mismatch between {} list serializer & unified list serializer'.format(cls)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_unified_job_detail_exclusive_fields():
|
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': ['some', 'invalid', 'list']}, 'expected to be string'),
|
||||||
({'type': 'password', 'default': False}, 'expected to be string'),
|
({'type': 'password', 'default': False}, 'expected to be string'),
|
||||||
({'type': 'integer', 'default': 'foo'}, 'expected to be int'),
|
({'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': 'foo'}, 'allowed question types'),
|
||||||
({'type': u'🐉'}, 'allowed question types'),
|
({'type': '🐉'}, 'allowed question types'),
|
||||||
({'type': 'multiplechoice'}, 'multiplechoice must specify choices'),
|
({'type': 'multiplechoice'}, 'multiplechoice must specify choices'),
|
||||||
({'type': 'integer', 'min': 'foo'}, 'min limit in survey question 0 expected to be integer'),
|
({'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."),
|
({'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='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='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='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:
|
with pytest.raises(ValidationError) as e:
|
||||||
proj.clean_credential()
|
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):
|
def job(mocker):
|
||||||
ret = mocker.MagicMock(
|
ret = mocker.MagicMock(
|
||||||
**{
|
**{
|
||||||
'decrypted_extra_vars.return_value': '{\"secret_key\": \"my_password\"}',
|
'decrypted_extra_vars.return_value': '{"secret_key": "my_password"}',
|
||||||
'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}',
|
'display_extra_vars.return_value': '{"secret_key": "$encrypted$"}',
|
||||||
'extra_vars_dict': {"secret_key": "my_password"},
|
'extra_vars_dict': {"secret_key": "my_password"},
|
||||||
'pk': 1,
|
'pk': 1,
|
||||||
'job_template.pk': 1,
|
'job_template.pk': 1,
|
||||||
@@ -313,7 +313,7 @@ class TestWorkflowSurveys:
|
|||||||
({'tmpl_var': 'bar2'}, False), # different value from template, not okay
|
({'tmpl_var': 'bar2'}, False), # different value from template, not okay
|
||||||
({'tmpl_var': 'bar', 'a': 2}, False), # extra key, not okay
|
({'tmpl_var': 'bar', 'a': 2}, False), # extra key, not okay
|
||||||
({'tmpl_var': 'bar', False: 2}, False), # Falsy key
|
({'tmpl_var': 'bar', False: 2}, False), # Falsy key
|
||||||
({'tmpl_var': 'bar', u'🐉': u'🐉'}, False), # dragons
|
({'tmpl_var': 'bar', '🐉': '🐉'}, False), # dragons
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class TestExtraVarsNoPrompt:
|
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):
|
def test_invalid__extra_data_system_jobs(extra_data):
|
||||||
accepted, rejected, errors = SystemJobTemplate(job_type='cleanup_jobs').accept_or_ignore_variables(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():
|
def test_unallowed_system_job_data():
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import awx.main.notifications.rocketchat_backend as rocketchat_backend
|
|||||||
|
|
||||||
|
|
||||||
def test_send_messages():
|
def test_send_messages():
|
||||||
with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.rocketchat_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.rocketchat_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 201
|
requests_mock.post.return_value.status_code = 201
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||||
backend = rocketchat_backend.RocketChatBackend()
|
backend = rocketchat_backend.RocketChatBackend()
|
||||||
@@ -92,9 +93,10 @@ def test_send_messages_with_icon_url():
|
|||||||
|
|
||||||
|
|
||||||
def test_send_messages_with_no_verify_ssl():
|
def test_send_messages_with_no_verify_ssl():
|
||||||
with mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.rocketchat_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.rocketchat_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.rocketchat_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 201
|
requests_mock.post.return_value.status_code = 201
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
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)
|
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():
|
def test_send_messages_as_POST():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 200
|
requests_mock.post.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||||
backend = webhook_backend.WebhookBackend('POST', None)
|
backend = webhook_backend.WebhookBackend('POST', None)
|
||||||
@@ -38,9 +39,10 @@ def test_send_messages_as_POST():
|
|||||||
|
|
||||||
|
|
||||||
def test_send_messages_as_PUT():
|
def test_send_messages_as_PUT():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.put.return_value.status_code = 200
|
requests_mock.put.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||||
backend = webhook_backend.WebhookBackend('PUT', None)
|
backend = webhook_backend.WebhookBackend('PUT', None)
|
||||||
@@ -69,9 +71,10 @@ def test_send_messages_as_PUT():
|
|||||||
|
|
||||||
|
|
||||||
def test_send_messages_with_username():
|
def test_send_messages_with_username():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 200
|
requests_mock.post.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||||
backend = webhook_backend.WebhookBackend('POST', None, username='userstring')
|
backend = webhook_backend.WebhookBackend('POST', None, username='userstring')
|
||||||
@@ -100,9 +103,10 @@ def test_send_messages_with_username():
|
|||||||
|
|
||||||
|
|
||||||
def test_send_messages_with_password():
|
def test_send_messages_with_password():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 200
|
requests_mock.post.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||||
backend = webhook_backend.WebhookBackend('POST', None, password='passwordstring')
|
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():
|
def test_send_messages_with_username_and_password():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 200
|
requests_mock.post.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
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')
|
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():
|
def test_send_messages_with_no_verify_ssl():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 200
|
requests_mock.post.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
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)
|
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():
|
def test_send_messages_with_additional_headers():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock:
|
mock.patch('awx.main.notifications.webhook_backend.get_awx_http_client_headers') as version_mock,
|
||||||
|
):
|
||||||
requests_mock.post.return_value.status_code = 200
|
requests_mock.post.return_value.status_code = 200
|
||||||
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
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'})
|
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():
|
def test_send_messages_with_redirects_ok():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_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
|
# First two calls return redirects, third call returns 200
|
||||||
requests_mock.post.side_effect = [
|
requests_mock.post.side_effect = [
|
||||||
mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
|
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():
|
def test_send_messages_with_redirects_blank():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_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
|
# First call returns a redirect with Location header, second call returns 301 but NO Location header
|
||||||
requests_mock.post.side_effect = [
|
requests_mock.post.side_effect = [
|
||||||
mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
|
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():
|
def test_send_messages_with_redirects_max_retries_exceeded():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_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
|
# Return MAX_RETRIES (5) redirect responses to exceed the retry limit
|
||||||
requests_mock.post.side_effect = [
|
requests_mock.post.side_effect = [
|
||||||
mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
|
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():
|
def test_send_messages_with_error_status_code():
|
||||||
with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
|
with (
|
||||||
'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
|
mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock,
|
||||||
) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_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
|
# Return a 404 error status code
|
||||||
requests_mock.post.return_value = mock.Mock(status_code=404)
|
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)'}
|
version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
|
||||||
|
|||||||
@@ -470,8 +470,7 @@ class TestIsWorkflowDone:
|
|||||||
True,
|
True,
|
||||||
smart_str(
|
smart_str(
|
||||||
_(
|
_(
|
||||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s) missing unified job template and error handling path []."
|
||||||
" missing unified job template and error handling path []."
|
|
||||||
).format(nodes[2].id, nodes[2].job.status)
|
).format(nodes[2].id, nodes[2].job.status)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -485,9 +484,9 @@ class TestIsWorkflowDone:
|
|||||||
assert g.has_workflow_failed() == (
|
assert g.has_workflow_failed() == (
|
||||||
True,
|
True,
|
||||||
smart_str(
|
smart_str(
|
||||||
_(
|
_("No error handling path for workflow job node(s) []. Workflow job node(s) missing unified job template and error handling path [{}].").format(
|
||||||
"No error handling path for workflow job node(s) []. Workflow job node(s) missing" " unified job template and error handling path [{}]."
|
nodes[2].id
|
||||||
).format(nodes[2].id)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -501,9 +500,9 @@ class TestIsWorkflowDone:
|
|||||||
assert g.has_workflow_failed() == (
|
assert g.has_workflow_failed() == (
|
||||||
True,
|
True,
|
||||||
smart_str(
|
smart_str(
|
||||||
_(
|
_("No error handling path for workflow job node(s) []. Workflow job node(s) missing unified job template and error handling path [{}].").format(
|
||||||
"No error handling path for workflow job node(s) []. Workflow job node(s) missing" " unified job template and error handling path [{}]."
|
nodes[0].id
|
||||||
).format(nodes[0].id)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -514,8 +513,7 @@ class TestIsWorkflowDone:
|
|||||||
True,
|
True,
|
||||||
smart_str(
|
smart_str(
|
||||||
_(
|
_(
|
||||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s) missing unified job template and error handling path []."
|
||||||
" missing unified job template and error handling path []."
|
|
||||||
).format(nodes[0].id, nodes[0].job.status)
|
).format(nodes[0].id, nodes[0].job.status)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -527,8 +525,7 @@ class TestIsWorkflowDone:
|
|||||||
True,
|
True,
|
||||||
smart_str(
|
smart_str(
|
||||||
_(
|
_(
|
||||||
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
"No error handling path for workflow job node(s) [({},{})]. Workflow job node(s) missing unified job template and error handling path []."
|
||||||
" missing unified job template and error handling path []."
|
|
||||||
).format(nodes[0].id, nodes[0].job.status)
|
).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': {'99': '{{awx_secret}}'}}, False),
|
||||||
({'env': {'AWX_SECRET=': '{{awx_secret}}'}}, False),
|
({'env': {'AWX_SECRET=': '{{awx_secret}}'}}, False),
|
||||||
({'env': {'ANSIBLE_SETTING': '{{awx_secret}}'}}, False),
|
({'env': {'ANSIBLE_SETTING': '{{awx_secret}}'}}, False),
|
||||||
({'env': {'DRAGON': u'🐉'}}, False),
|
({'env': {'DRAGON': '🐉'}}, False),
|
||||||
({'env': {u'🐉': 'DRAGON'}}, False),
|
({'env': {'🐉': 'DRAGON'}}, False),
|
||||||
({'extra_vars': 123}, False),
|
({'extra_vars': 123}, False),
|
||||||
({'extra_vars': {}}, True),
|
({'extra_vars': {}}, True),
|
||||||
({'extra_vars': {'hostname': '{{host}}'}}, True),
|
({'extra_vars': {'hostname': '{{host}}'}}, True),
|
||||||
@@ -306,7 +306,8 @@ def test_implicit_role_field_parents():
|
|||||||
second_field_descriptor = getattr(cls, field_name)
|
second_field_descriptor = getattr(cls, field_name)
|
||||||
# all supported linkage types
|
# all supported linkage types
|
||||||
assert isinstance(
|
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
|
# only these links are supported
|
||||||
if field_attr:
|
if field_attr:
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ uri = URI(scheme="https", username="myusername", password="mypasswordwith%40", h
|
|||||||
TEST_CLEARTEXT.append(
|
TEST_CLEARTEXT.append(
|
||||||
{
|
{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'text': textwrap.dedent("""\
|
'text': textwrap.dedent(
|
||||||
|
"""\
|
||||||
PLAY [all] ********************************************************************
|
PLAY [all] ********************************************************************
|
||||||
|
|
||||||
TASK: [delete project directory before update] ********************************
|
TASK: [delete project directory before update] ********************************
|
||||||
@@ -58,7 +59,9 @@ TEST_CLEARTEXT.append(
|
|||||||
|
|
||||||
localhost : ok=0 changed=0 unreachable=0 failed=1
|
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,
|
'host_occurrences': 2,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -67,7 +70,8 @@ uri = URI(scheme="https", username="Dhh3U47nmC26xk9PKscV", password="PXPfWW8YzYr
|
|||||||
TEST_CLEARTEXT.append(
|
TEST_CLEARTEXT.append(
|
||||||
{
|
{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'text': textwrap.dedent("""\
|
'text': textwrap.dedent(
|
||||||
|
"""\
|
||||||
TASK: [update project using git] **
|
TASK: [update project using git] **
|
||||||
failed: [localhost] => {"cmd": "/usr/bin/git ls-remote https://REDACTED:********", "failed": true, "rc": 128}
|
failed: [localhost] => {"cmd": "/usr/bin/git ls-remote https://REDACTED:********", "failed": true, "rc": 128}
|
||||||
stderr: error: Couldn't resolve host '@%s' while accessing %s
|
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
|
msg: error: Couldn't resolve host '@%s' while accessing %s
|
||||||
|
|
||||||
fatal: HTTP request failed
|
fatal: HTTP request failed
|
||||||
""" % (uri.host, str(uri), uri.host, str(uri))),
|
"""
|
||||||
|
% (uri.host, str(uri), uri.host, str(uri))
|
||||||
|
),
|
||||||
'host_occurrences': 4,
|
'host_occurrences': 4,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1303,10 +1303,10 @@ class TestInventoryUpdateCredentials(TestJobExecution):
|
|||||||
if credential:
|
if credential:
|
||||||
credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
|
credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
|
||||||
|
|
||||||
env["VMWARE_USER"] == "bob",
|
(env["VMWARE_USER"] == "bob",)
|
||||||
env["VMWARE_PASSWORD"] == "secret",
|
(env["VMWARE_PASSWORD"] == "secret",)
|
||||||
env["VMWARE_HOST"] == "https://example.org",
|
(env["VMWARE_HOST"] == "https://example.org",)
|
||||||
env["VMWARE_VALIDATE_CERTS"] == "False",
|
(env["VMWARE_VALIDATE_CERTS"] == "False",)
|
||||||
|
|
||||||
def test_azure_rm_source_with_tenant(self, private_data_dir, inventory_update, mocker, mock_me):
|
def test_azure_rm_source_with_tenant(self, private_data_dir, inventory_update, mocker, mock_me):
|
||||||
task = jobs.RunInventoryUpdate()
|
task = jobs.RunInventoryUpdate()
|
||||||
|
|||||||
@@ -163,13 +163,13 @@ def memoized_function(mocker, mock_cache):
|
|||||||
|
|
||||||
def test_memoize_track_function(memoized_function, mock_cache):
|
def test_memoize_track_function(memoized_function, mock_cache):
|
||||||
assert memoized_function('scott', 'scotterson') == 'scotterson'
|
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('scott', 'scotterson') == 'scotterson'
|
||||||
|
|
||||||
assert memoized_function.calls['scott'] == 1
|
assert memoized_function.calls['scott'] == 1
|
||||||
|
|
||||||
assert memoized_function('john', 'smith') == 'smith'
|
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('john', 'smith') == 'smith'
|
||||||
|
|
||||||
assert memoized_function.calls['john'] == 1
|
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('john', 'smith') == 'smith'
|
||||||
assert memoized_function.calls['john'] == 1
|
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):
|
with mock.patch('awx.main.utils.common.memoize_delete', side_effect=mock_cache.delete):
|
||||||
common.memoize_delete('myfunction')
|
common.memoize_delete('myfunction')
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def test_encrypt_field_without_pk():
|
|||||||
|
|
||||||
|
|
||||||
def test_encrypt_field_with_unicode_string():
|
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)
|
field = Setting(value=value)
|
||||||
encrypted = field.value = encryption.encrypt_field(field, 'value')
|
encrypted = field.value = encryption.encrypt_field(field, 'value')
|
||||||
assert encryption.decrypt_field(field, 'value') == value
|
assert encryption.decrypt_field(field, 'value') == value
|
||||||
|
|||||||
@@ -74,16 +74,16 @@ class TestSmartFilterQueryFromString:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"filter_string,q_expected",
|
"filter_string,q_expected",
|
||||||
[
|
[
|
||||||
('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})),
|
('facts__facts__blank=""', Q(**{"facts__facts__blank": ""})),
|
||||||
('"facts__facts__ space "="f"', Q(**{u"facts__facts__ space ": u"f"})),
|
('"facts__facts__ space "="f"', Q(**{"facts__facts__ space ": "f"})),
|
||||||
('"facts__facts__ e "=no_quotes_here', Q(**{u"facts__facts__ e ": u"no_quotes_here"})),
|
('"facts__facts__ e "=no_quotes_here', Q(**{"facts__facts__ e ": "no_quotes_here"})),
|
||||||
('a__b__c=3', Q(**{u"a__b__c": 3})),
|
('a__b__c=3', Q(**{"a__b__c": 3})),
|
||||||
('a__b__c=3.14', Q(**{u"a__b__c": 3.14})),
|
('a__b__c=3.14', Q(**{"a__b__c": 3.14})),
|
||||||
('a__b__c=true', Q(**{u"a__b__c": True})),
|
('a__b__c=true', Q(**{"a__b__c": True})),
|
||||||
('a__b__c=false', Q(**{u"a__b__c": False})),
|
('a__b__c=false', Q(**{"a__b__c": False})),
|
||||||
('a__b__c=null', Q(**{u"a__b__c": None})),
|
('a__b__c=null', Q(**{"a__b__c": None})),
|
||||||
('ansible_facts__a="true"', Q(**{u"ansible_facts__contains": {u"a": u"true"}})),
|
('ansible_facts__a="true"', Q(**{"ansible_facts__contains": {"a": "true"}})),
|
||||||
('ansible_facts__a__exact="true"', Q(**{u"ansible_facts__contains": {u"a": u"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"})),
|
||||||
# ('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):
|
def test_invalid_filter_strings(self, mock_get_host_model, filter_string):
|
||||||
with pytest.raises(RuntimeError) as e:
|
with pytest.raises(RuntimeError) as e:
|
||||||
SmartFilter.query_from_string(filter_string)
|
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(
|
@pytest.mark.parametrize(
|
||||||
"filter_string",
|
"filter_string",
|
||||||
@@ -118,8 +118,8 @@ class TestSmartFilterQueryFromString:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"filter_string,q_expected",
|
"filter_string,q_expected",
|
||||||
[
|
[
|
||||||
(u'(a=abc\u1f5e3def)', Q(**{u"a": u"abc\u1f5e3def"})),
|
('(a=abc\u1f5e3def)', Q(**{"a": "abc\u1f5e3def"})),
|
||||||
(u'(ansible_facts__a=abc\u1f5e3def)', Q(**{u"ansible_facts__contains": {u"a": u"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):
|
def test_unicode(self, mock_get_host_model, filter_string, q_expected):
|
||||||
@@ -129,17 +129,14 @@ class TestSmartFilterQueryFromString:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"filter_string,q_expected",
|
"filter_string,q_expected",
|
||||||
[
|
[
|
||||||
('(a=b)', Q(**{u"a": u"b"})),
|
('(a=b)', Q(**{"a": "b"})),
|
||||||
('a=b and c=d', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})),
|
('a=b and c=d', Q(**{"a": "b"}) & Q(**{"c": "d"})),
|
||||||
('(a=b and c=d)', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})),
|
('(a=b and c=d)', Q(**{"a": "b"}) & Q(**{"c": "d"})),
|
||||||
('a=b or c=d', Q(**{u"a": u"b"}) | Q(**{u"c": u"d"})),
|
('a=b or c=d', Q(**{"a": "b"}) | Q(**{"c": "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 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',
|
'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(**{"a": "b"}) | Q(**{"a": "d"}) | Q(**{"a": "e"}) | Q(**{"a": "z"}) & Q(**{"b": "h"}) & Q(**{"b": "i"}) & Q(**{"b": "j"}) & Q(**{"b": "k"}),
|
||||||
| 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"}),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -150,21 +147,21 @@ class TestSmartFilterQueryFromString:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"filter_string,q_expected",
|
"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', Q(**{"ansible_facts__contains": {"a": {"b": {"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[]=3.14', Q(**{"ansible_facts__contains": {"a": {"b": {"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[]=true', Q(**{"ansible_facts__contains": {"a": {"b": {"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[]=false', Q(**{"ansible_facts__contains": {"a": {"b": {"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[]="true"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": ["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[]="hello world"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": ["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(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": ["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="foobar"', Q(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": "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(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": "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(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": ["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[]__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")',
|
'(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(**{"ansible_facts__contains": {"a": {"b": {"c": [{"d": {"e": {"f": ["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": ["foobar"]}}]}}}}),
|
||||||
),
|
),
|
||||||
# ('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
|
# ('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
|
||||||
# ('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",
|
"filter_string,q_expected",
|
||||||
[
|
[
|
||||||
# ('a__b__c[]="true"', Q(**{u"a__b__c__contains": u"\"true\""})),
|
# ('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"})),
|
||||||
# ('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(
|
@pytest.mark.parametrize(
|
||||||
"filter_string,q_expected",
|
"filter_string,q_expected",
|
||||||
[
|
[
|
||||||
('ansible_facts__a=null', Q(**{u"ansible_facts__contains": {u"a": None}})),
|
('ansible_facts__a=null', Q(**{"ansible_facts__contains": {"a": None}})),
|
||||||
('ansible_facts__c="null"', Q(**{u"ansible_facts__contains": {u"c": u"\"null\""}})),
|
('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):
|
def test_contains_query_generated_null(self, mock_get_host_model, filter_string, q_expected):
|
||||||
@@ -201,17 +198,17 @@ class TestSmartFilterQueryFromString:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"filter_string,q_expected",
|
"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',
|
'search=foo and group__search=foo',
|
||||||
Q(
|
Q(
|
||||||
Q(**{u"name__icontains": u"foo"}) | Q(**{u"description__icontains": u"foo"}),
|
Q(**{"name__icontains": "foo"}) | Q(**{"description__icontains": "foo"}),
|
||||||
Q(**{u"group__name__icontains": u"foo"}) | Q(**{u"group__description__icontains": u"foo"}),
|
Q(**{"group__name__icontains": "foo"}) | Q(**{"group__description__icontains": "foo"}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'search=foo or ansible_facts__a=null',
|
'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():
|
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():
|
def test_kv_unsafe_in_list():
|
||||||
|
|||||||
@@ -14,16 +14,14 @@ def test_validate_rh_basic_auth_rhsm():
|
|||||||
"""
|
"""
|
||||||
licenser = Licenser()
|
licenser = Licenser()
|
||||||
|
|
||||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
with (
|
||||||
licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'
|
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||||
) as mock_get_host, patch.object(licenser, 'get_rhsm_subs', return_value=[]) as mock_get_rhsm, patch.object(
|
patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com') as mock_get_host,
|
||||||
licenser, 'get_satellite_subs'
|
patch.object(licenser, 'get_rhsm_subs', return_value=[]) as mock_get_rhsm,
|
||||||
) as mock_get_satellite, patch.object(
|
patch.object(licenser, 'get_satellite_subs') as mock_get_satellite,
|
||||||
licenser, 'get_crc_subs'
|
patch.object(licenser, 'get_crc_subs') as mock_get_crc,
|
||||||
) as mock_get_crc, patch.object(
|
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||||
licenser, 'generate_license_options_from_entitlements'
|
):
|
||||||
) as mock_generate:
|
|
||||||
|
|
||||||
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||||
|
|
||||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
||||||
@@ -44,16 +42,14 @@ def test_validate_rh_basic_auth_satellite():
|
|||||||
"""
|
"""
|
||||||
licenser = Licenser()
|
licenser = Licenser()
|
||||||
|
|
||||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
with (
|
||||||
licenser, 'get_host_from_rhsm_config', return_value='https://satellite.example.com'
|
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||||
) as mock_get_host, patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm, patch.object(
|
patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://satellite.example.com') as mock_get_host,
|
||||||
licenser, 'get_satellite_subs', return_value=[]
|
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||||
) as mock_get_satellite, patch.object(
|
patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite,
|
||||||
licenser, 'get_crc_subs'
|
patch.object(licenser, 'get_crc_subs') as mock_get_crc,
|
||||||
) as mock_get_crc, patch.object(
|
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||||
licenser, 'generate_license_options_from_entitlements'
|
):
|
||||||
) as mock_generate:
|
|
||||||
|
|
||||||
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||||
|
|
||||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
||||||
@@ -72,14 +68,14 @@ def test_validate_rh_service_account_crc():
|
|||||||
"""
|
"""
|
||||||
licenser = Licenser()
|
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(
|
with (
|
||||||
licenser, 'get_rhsm_subs'
|
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||||
) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs') as mock_get_satellite, patch.object(
|
patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host,
|
||||||
licenser, 'get_crc_subs', return_value=[]
|
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||||
) as mock_get_crc, patch.object(
|
patch.object(licenser, 'get_satellite_subs') as mock_get_satellite,
|
||||||
licenser, 'generate_license_options_from_entitlements'
|
patch.object(licenser, 'get_crc_subs', return_value=[]) as mock_get_crc,
|
||||||
) as mock_generate:
|
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||||
|
):
|
||||||
mock_settings.SUBSCRIPTIONS_RHSM_URL = 'https://console.redhat.com/api/rhsm/v1/subscriptions'
|
mock_settings.SUBSCRIPTIONS_RHSM_URL = 'https://console.redhat.com/api/rhsm/v1/subscriptions'
|
||||||
|
|
||||||
licenser.validate_rh('client_id', 'client_secret', basic_auth=False)
|
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()
|
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(
|
with (
|
||||||
licenser, 'get_rhsm_subs'
|
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||||
) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite, patch.object(
|
patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host,
|
||||||
licenser, 'get_crc_subs'
|
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||||
) as mock_get_crc, patch.object(
|
patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite,
|
||||||
licenser, 'generate_license_options_from_entitlements'
|
patch.object(licenser, 'get_crc_subs') as mock_get_crc,
|
||||||
) as mock_generate:
|
patch.object(licenser, 'generate_license_options_from_entitlements') as mock_generate,
|
||||||
|
):
|
||||||
mock_settings.REDHAT_CANDLEPIN_HOST = 'https://satellite.example.com'
|
mock_settings.REDHAT_CANDLEPIN_HOST = 'https://satellite.example.com'
|
||||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
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"""
|
"""REDHAT_CANDLEPIN_HOST without a scheme gets https:// prepended"""
|
||||||
licenser = Licenser()
|
licenser = Licenser()
|
||||||
|
|
||||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(licenser, 'get_host_from_rhsm_config'), patch.object(
|
with (
|
||||||
licenser, 'get_rhsm_subs'
|
patch('awx.main.utils.licensing.settings') as mock_settings,
|
||||||
) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite, patch.object(
|
patch.object(licenser, 'get_host_from_rhsm_config'),
|
||||||
licenser, 'get_crc_subs'
|
patch.object(licenser, 'get_rhsm_subs') as mock_get_rhsm,
|
||||||
), patch.object(
|
patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite,
|
||||||
licenser, 'generate_license_options_from_entitlements'
|
patch.object(licenser, 'get_crc_subs'),
|
||||||
|
patch.object(licenser, 'generate_license_options_from_entitlements'),
|
||||||
):
|
):
|
||||||
|
|
||||||
mock_settings.REDHAT_CANDLEPIN_HOST = 'satellite.example.com'
|
mock_settings.REDHAT_CANDLEPIN_HOST = 'satellite.example.com'
|
||||||
licenser.validate_rh('testuser', 'testpass', basic_auth=True)
|
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"""
|
"""Test validate_rh raises ValueError when user is missing"""
|
||||||
licenser = Licenser()
|
licenser = Licenser()
|
||||||
|
|
||||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
with (
|
||||||
licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'
|
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
|
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||||
with pytest.raises(ValueError, match='subscriptions_client_id or subscriptions_username is required'):
|
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"""
|
"""Test validate_rh raises ValueError when password is missing"""
|
||||||
licenser = Licenser()
|
licenser = Licenser()
|
||||||
|
|
||||||
with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
|
with (
|
||||||
licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'
|
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
|
mock_settings.REDHAT_CANDLEPIN_HOST = None
|
||||||
with pytest.raises(ValueError, match='subscriptions_client_secret or subscriptions_password is required'):
|
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 = ''
|
netloc_password = ''
|
||||||
|
|
||||||
if netloc_username and parts.scheme != 'file' and scm_type not in ("insights", "archive"):
|
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:
|
else:
|
||||||
netloc = u''
|
netloc = ''
|
||||||
# urllib.parse strips brackets from IPv6 addresses, so we need to add them back in
|
# urllib.parse strips brackets from IPv6 addresses, so we need to add them back in
|
||||||
hostname = parts.hostname
|
hostname = parts.hostname
|
||||||
if hostname and ':' in hostname and '[' in url and ']' in url:
|
if hostname and ':' in hostname and '[' in url and ']' in url:
|
||||||
hostname = f'[{hostname}]'
|
hostname = f'[{hostname}]'
|
||||||
netloc = u'@'.join(filter(None, [netloc, hostname]))
|
netloc = '@'.join(filter(None, [netloc, hostname]))
|
||||||
if parts.port:
|
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])
|
new_url = urllib.parse.urlunsplit([parts.scheme, netloc, parts.path, parts.query, parts.fragment])
|
||||||
if scp_format and parts.scheme == 'git+ssh':
|
if scp_format and parts.scheme == 'git+ssh':
|
||||||
new_url = new_url.replace('git+ssh://', '', 1).replace('/', ':', 1)
|
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:
|
if password_fields is None:
|
||||||
password_fields = set(getattr(type(obj), 'PASSWORD_FIELDS', [])) | set(['password'])
|
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$')):
|
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):
|
if hasattr(obj, 'display_%s' % field_name):
|
||||||
field_val = getattr(obj, 'display_%s' % field_name)()
|
field_val = getattr(obj, 'display_%s' % field_name)()
|
||||||
if isinstance(field_val, (list, dict)):
|
if isinstance(field_val, (list, dict)):
|
||||||
@@ -1126,15 +1126,15 @@ def truncate_stdout(stdout, size):
|
|||||||
if size <= 0 or len(stdout) <= size:
|
if size <= 0 or len(stdout) <= size:
|
||||||
return stdout
|
return stdout
|
||||||
|
|
||||||
stdout = stdout[: (size - 1)] + u'\u2026'
|
stdout = stdout[: (size - 1)] + '\u2026'
|
||||||
set_count, reset_count = 0, 0
|
set_count, reset_count = 0, 0
|
||||||
for m in ANSI_SGR_PATTERN.finditer(stdout):
|
for m in ANSI_SGR_PATTERN.finditer(stdout):
|
||||||
if m.group() == u'\u001b[0m':
|
if m.group() == '\u001b[0m':
|
||||||
reset_count += 1
|
reset_count += 1
|
||||||
else:
|
else:
|
||||||
set_count += 1
|
set_count += 1
|
||||||
|
|
||||||
return stdout + u'\u001b[0m' * (set_count - reset_count)
|
return stdout + '\u001b[0m' * (set_count - reset_count)
|
||||||
|
|
||||||
|
|
||||||
def deepmerge(a, b):
|
def deepmerge(a, b):
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ def db_requirement_violations() -> Optional[str]:
|
|||||||
if os.getenv('SKIP_PG_VERSION_CHECK', False):
|
if os.getenv('SKIP_PG_VERSION_CHECK', False):
|
||||||
return None
|
return None
|
||||||
if connection.vendor == 'postgresql':
|
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
|
# 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.
|
# 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
|
# The return of connection.pg_version is something like 12013
|
||||||
|
|||||||
+10
-10
@@ -118,11 +118,11 @@ class DynamicLevelFilter(Filter):
|
|||||||
|
|
||||||
|
|
||||||
def string_to_type(t):
|
def string_to_type(t):
|
||||||
if t == u'null':
|
if t == 'null':
|
||||||
return None
|
return None
|
||||||
if t == u'true':
|
if t == 'true':
|
||||||
return True
|
return True
|
||||||
elif t == u'false':
|
elif t == 'false':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if re.search(r'^[-+]?[0-9]+$', t):
|
if re.search(r'^[-+]?[0-9]+$', t):
|
||||||
@@ -151,7 +151,7 @@ class SmartFilter(object):
|
|||||||
search_kwargs = self._expand_search(k, v)
|
search_kwargs = self._expand_search(k, v)
|
||||||
if search_kwargs:
|
if search_kwargs:
|
||||||
kwargs.update(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)
|
self.result = Host.objects.filter(q)
|
||||||
else:
|
else:
|
||||||
# detect loops and restrict access to sensitive fields
|
# detect loops and restrict access to sensitive fields
|
||||||
@@ -168,7 +168,7 @@ class SmartFilter(object):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
def strip_quotes_json_logic(self, 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[1:-1]
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@@ -203,9 +203,9 @@ class SmartFilter(object):
|
|||||||
strip_len = len(SmartFilter.SEARCHABLE_RELATIONSHIP)
|
strip_len = len(SmartFilter.SEARCHABLE_RELATIONSHIP)
|
||||||
k = k[strip_len:]
|
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
|
assembled_v = None
|
||||||
|
|
||||||
last_v = None
|
last_v = None
|
||||||
@@ -213,7 +213,7 @@ class SmartFilter(object):
|
|||||||
|
|
||||||
for i, piece in enumerate(pieces):
|
for i, piece in enumerate(pieces):
|
||||||
new_kv = dict()
|
new_kv = dict()
|
||||||
if piece.endswith(u'[]'):
|
if piece.endswith('[]'):
|
||||||
new_v = []
|
new_v = []
|
||||||
new_kv[piece[0:-2]] = new_v
|
new_kv[piece[0:-2]] = new_v
|
||||||
else:
|
else:
|
||||||
@@ -241,7 +241,7 @@ class SmartFilter(object):
|
|||||||
|
|
||||||
def _extract_key_value(self, t):
|
def _extract_key_value(self, t):
|
||||||
k = t[0]
|
k = t[0]
|
||||||
v = t[1] if len(t) > 1 else u""
|
v = t[1] if len(t) > 1 else ""
|
||||||
|
|
||||||
# Strip quotes from key
|
# Strip quotes from key
|
||||||
if isinstance(k, str) and k.startswith('"') and k.endswith('"'):
|
if isinstance(k, str) and k.startswith('"') and k.endswith('"'):
|
||||||
@@ -333,7 +333,7 @@ class SmartFilter(object):
|
|||||||
try:
|
try:
|
||||||
res = bool_expr.parse_string(filter_string, parse_all=True)
|
res = bool_expr.parse_string(filter_string, parse_all=True)
|
||||||
except (pp.ParseException, FieldDoesNotExist):
|
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:
|
if len(res) > 0:
|
||||||
return res[0].result
|
return res[0].result
|
||||||
|
|||||||
@@ -265,9 +265,7 @@ class Licenser(object):
|
|||||||
except requests.exceptions.ConnectionError as error:
|
except requests.exceptions.ConnectionError as error:
|
||||||
raise error
|
raise error
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
raise OSError(
|
raise OSError('Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)) from error # noqa
|
||||||
'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()
|
subs.raise_for_status()
|
||||||
|
|
||||||
for sub in subs.json():
|
for sub in subs.json():
|
||||||
@@ -328,9 +326,7 @@ class Licenser(object):
|
|||||||
except requests.exceptions.ConnectionError as error:
|
except requests.exceptions.ConnectionError as error:
|
||||||
raise error
|
raise error
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
raise OSError(
|
raise OSError('Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)) from error # noqa
|
||||||
'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()
|
orgs.raise_for_status()
|
||||||
|
|
||||||
for org in orgs.json()['results']:
|
for org in orgs.json()['results']:
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class MemInventory(object):
|
|||||||
try:
|
try:
|
||||||
port = int(name.split(':')[1])
|
port = int(name.split(':')[1])
|
||||||
except (ValueError, UnicodeDecodeError):
|
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
|
port = None
|
||||||
else:
|
else:
|
||||||
host_name = name
|
host_name = name
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SafeLoader(yaml.Loader):
|
|||||||
return node
|
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):
|
def safe_dump(x, safe_dict=None):
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class AdHocCommand(HasCreate, UnifiedJob):
|
|||||||
verbosity=verbosity,
|
verbosity=verbosity,
|
||||||
inventory=self.ds.inventory,
|
inventory=self.ds.inventory,
|
||||||
credential=self.ds.credential,
|
credential=self.ds.credential,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||||
return payload
|
return payload
|
||||||
@@ -53,7 +53,7 @@ class AdHocCommand(HasCreate, UnifiedJob):
|
|||||||
verbosity=verbosity,
|
verbosity=verbosity,
|
||||||
inventory=inventory,
|
inventory=inventory,
|
||||||
credential=credential,
|
credential=credential,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
return self.update_identity(AdHocCommands(self.connection).post(payload))
|
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,
|
organization=organization.id if organization else None,
|
||||||
credential=credential.id if credential else None,
|
credential=credential.id if credential else None,
|
||||||
pull=pull,
|
pull=pull,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
|||||||
scm_url=scm_url,
|
scm_url=scm_url,
|
||||||
scm_branch=scm_branch,
|
scm_branch=scm_branch,
|
||||||
credential=credential,
|
credential=credential,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||||
return payload
|
return payload
|
||||||
@@ -84,7 +84,7 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
|||||||
scm_branch=scm_branch,
|
scm_branch=scm_branch,
|
||||||
organization=organization,
|
organization=organization,
|
||||||
credential=credential,
|
credential=credential,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
self.update_identity(Projects(self.connection).post(payload))
|
self.update_identity(Projects(self.connection).post(payload))
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class CLI(object):
|
|||||||
self.root.connection.login(username, password)
|
self.root.connection.login(username, password)
|
||||||
self.root.get()
|
self.root.get()
|
||||||
except Exception as e:
|
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
|
return
|
||||||
|
|
||||||
# Use session-based authentication (default)
|
# Use session-based authentication (default)
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class ProjectCreate(CustomAction):
|
|||||||
resource = 'projects'
|
resource = 'projects'
|
||||||
|
|
||||||
def add_arguments(self, parser, resource_options_parser):
|
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.')
|
parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the new project has updated.')
|
||||||
|
|
||||||
def post(self, kwargs):
|
def post(self, kwargs):
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ class CircularDependencyError(ValueError):
|
|||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
# Sort the data just to make the output consistent, for use in
|
# Sort the data just to make the output consistent, for use in
|
||||||
# error messages. That's convenient for doctests.
|
# error messages. That's convenient for doctests.
|
||||||
s = 'Circular dependencies exist among these items: {{{}}}'.format(
|
s = 'Circular dependencies exist among these items: {{{}}}'.format(', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))) # noqa
|
||||||
', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))
|
|
||||||
) # noqa
|
|
||||||
super(CircularDependencyError, self).__init__(s)
|
super(CircularDependencyError, self).__init__(s)
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user