diff --git a/Makefile b/Makefile index 2d6b276aab..aa8b304982 100644 --- a/Makefile +++ b/Makefile @@ -607,7 +607,7 @@ clean-elk: docker rm tools_kibana_1 psql-container: - docker run -it --net tools_default --rm postgres:9.4.1 sh -c 'exec psql -h "postgres" -p "5432" -U postgres' + docker run -it --net tools_default --rm postgres:9.6 sh -c 'exec psql -h "postgres" -p "5432" -U postgres' VERSION: @echo $(VERSION_TARGET) > $@ diff --git a/awx/api/filters.py b/awx/api/filters.py index e4d45bece9..841acc87cd 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -166,7 +166,13 @@ class FieldLookupBackend(BaseFilterBackend): elif isinstance(field, models.BooleanField): return to_python_boolean(value) elif isinstance(field, (ForeignObjectRel, ManyToManyField, GenericForeignKey, ForeignKey)): - return self.to_python_related(value) + try: + return self.to_python_related(value) + except ValueError: + raise ParseError(_('Invalid {field_name} id: {field_id}').format( + field_name=getattr(field, 'name', 'related field'), + field_id=value) + ) else: return field.to_python(value) @@ -243,11 +249,10 @@ class FieldLookupBackend(BaseFilterBackend): # Search across related objects. if key.endswith('__search'): for value in values: - for search_term in force_text(value).replace(',', ' ').split(): - search_value, new_keys = self.value_to_python(queryset.model, key, search_term) - assert isinstance(new_keys, list) - for new_key in new_keys: - search_filters.append((new_key, search_value)) + search_value, new_keys = self.value_to_python(queryset.model, key, force_text(value)) + assert isinstance(new_keys, list) + for new_key in new_keys: + search_filters.append((new_key, search_value)) continue # Custom chain__ and or__ filters, mutually exclusive (both can diff --git a/awx/api/generics.py b/awx/api/generics.py index 581fddb861..dfd0f30e6a 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -21,7 +21,7 @@ from django.utils.translation import ugettext_lazy as _ # Django REST Framework from rest_framework.authentication import get_authorization_header -from rest_framework.exceptions import PermissionDenied +from rest_framework.exceptions import PermissionDenied, AuthenticationFailed from rest_framework import generics from rest_framework.response import Response from rest_framework import status @@ -38,9 +38,10 @@ from awx.api.metadata import SublistAttachDetatchMetadata __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', 'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView', + 'SubListDestroyAPIView', 'SubListCreateAttachDetachAPIView', 'RetrieveAPIView', 'RetrieveUpdateAPIView', 'RetrieveDestroyAPIView', - 'RetrieveUpdateDestroyAPIView', 'DestroyAPIView', + 'RetrieveUpdateDestroyAPIView', 'SubDetailAPIView', 'ResourceAccessList', 'ParentMixin', @@ -115,6 +116,10 @@ class APIView(views.APIView): drf_request = super(APIView, self).initialize_request(request, *args, **kwargs) request.drf_request = drf_request + try: + request.drf_request_user = getattr(drf_request, 'user', False) + except AuthenticationFailed: + request.drf_request_user = None return drf_request def finalize_response(self, request, response, *args, **kwargs): @@ -140,7 +145,6 @@ class APIView(views.APIView): response['X-API-Query-Count'] = len(q_times) response['X-API-Query-Time'] = '%0.3fs' % sum(q_times) - analytics_logger.info("api response", extra=dict(python_objects=dict(request=request, response=response))) return response def get_authenticate_header(self, request): @@ -442,6 +446,41 @@ class SubListAPIView(ParentMixin, ListAPIView): return qs & sublist_qs +class DestroyAPIView(generics.DestroyAPIView): + + def has_delete_permission(self, obj): + return self.request.user.can_access(self.model, 'delete', obj) + + def perform_destroy(self, instance, check_permission=True): + if check_permission and not self.has_delete_permission(instance): + raise PermissionDenied() + super(DestroyAPIView, self).perform_destroy(instance) + + +class SubListDestroyAPIView(DestroyAPIView, SubListAPIView): + """ + Concrete view for deleting everything related by `relationship`. + """ + check_sub_obj_permission = True + + def destroy(self, request, *args, **kwargs): + instance_list = self.get_queryset() + if (not self.check_sub_obj_permission and + not request.user.can_access(self.parent_model, 'delete', self.get_parent_object())): + raise PermissionDenied() + self.perform_list_destroy(instance_list) + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_list_destroy(self, instance_list): + if self.check_sub_obj_permission: + # Check permissions for all before deleting, avoiding half-deleted lists + for instance in instance_list: + if self.has_delete_permission(instance): + raise PermissionDenied() + for instance in instance_list: + self.perform_destroy(instance, check_permission=False) + + class SubListCreateAPIView(SubListAPIView, ListCreateAPIView): # Base class for a sublist view that allows for creating subobjects # associated with the parent object. @@ -680,22 +719,11 @@ class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView): pass -class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView): - - def destroy(self, request, *args, **kwargs): - # somewhat lame that delete has to call it's own permissions check - obj = self.get_object() - if not request.user.can_access(self.model, 'delete', obj): - raise PermissionDenied() - obj.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - -class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView): +class RetrieveDestroyAPIView(RetrieveAPIView, DestroyAPIView): pass -class DestroyAPIView(GenericAPIView, generics.DestroyAPIView): +class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, DestroyAPIView): pass diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 83dddd6cef..75b2678e73 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -345,7 +345,9 @@ class BaseSerializer(serializers.ModelSerializer): continue summary_fields[fk] = OrderedDict() for field in related_fields: - if field == 'credential_type_id' and fk == 'credential' and self.version < 2: # TODO: remove version check in 3.3 + if ( + self.version < 2 and field == 'credential_type_id' and + fk in ['credential', 'vault_credential']): # TODO: remove version check in 3.3 continue fval = getattr(fkval, field, None) @@ -1111,8 +1113,13 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer): def get_related(self, obj): res = super(ProjectUpdateSerializer, self).get_related(obj) + try: + res.update(dict( + project = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}), + )) + except ObjectDoesNotExist: + pass res.update(dict( - project = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}), cancel = self.reverse('api:project_update_cancel', kwargs={'pk': obj.pk}), scm_inventory_updates = self.reverse('api:project_update_scm_inventory_updates', kwargs={'pk': obj.pk}), notifications = self.reverse('api:project_update_notifications_list', kwargs={'pk': obj.pk}), @@ -1726,8 +1733,15 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri def get_related(self, obj): res = super(InventoryUpdateSerializer, self).get_related(obj) + try: + res.update(dict( + inventory_source = self.reverse( + 'api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk} + ), + )) + except ObjectDoesNotExist: + pass res.update(dict( - inventory_source = self.reverse('api:inventory_source_detail', kwargs={'pk': obj.inventory_source.pk}), cancel = self.reverse('api:inventory_update_cancel', kwargs={'pk': obj.pk}), notifications = self.reverse('api:inventory_update_notifications_list', kwargs={'pk': obj.pk}), )) @@ -2125,7 +2139,7 @@ class CredentialSerializer(BaseSerializer): def to_internal_value(self, data): # TODO: remove when API v1 is removed - if 'credential_type' not in data: + if 'credential_type' not in data and self.version == 1: # If `credential_type` is not provided, assume the payload is a # v1 credential payload that specifies a `kind` and a flat list # of field values @@ -2162,10 +2176,22 @@ class CredentialSerializer(BaseSerializer): def validate_credential_type(self, credential_type): if self.instance and credential_type.pk != self.instance.credential_type.pk: - raise ValidationError( - _('You cannot change the credential type of the credential, as it may break the functionality' - ' of the resources using it.'), - ) + for rel in ( + 'ad_hoc_commands', + 'insights_inventories', + 'inventorysources', + 'inventoryupdates', + 'jobs', + 'jobtemplates', + 'projects', + 'projectupdates', + 'workflowjobnodes' + ): + if getattr(self.instance, rel).count() > 0: + raise ValidationError( + _('You cannot change the credential type of the credential, as it may break the functionality' + ' of the resources using it.'), + ) return credential_type @@ -2346,14 +2372,30 @@ class JobOptionsSerializer(LabelsListMixin, BaseSerializer): def get_related(self, obj): res = super(JobOptionsSerializer, self).get_related(obj) res['labels'] = self.reverse('api:job_template_label_list', kwargs={'pk': obj.pk}) - if obj.inventory: - res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) - if obj.project: - res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}) - if obj.credential: - res['credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.credential}) - if obj.vault_credential: - res['vault_credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.vault_credential}) + try: + if obj.inventory: + res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) + except ObjectDoesNotExist: + setattr(obj, 'inventory', None) + try: + if obj.project: + res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}) + except ObjectDoesNotExist: + setattr(obj, 'project', None) + try: + if obj.credential: + res['credential'] = self.reverse( + 'api:credential_detail', kwargs={'pk': obj.credential.pk} + ) + except ObjectDoesNotExist: + setattr(obj, 'credential', None) + try: + if obj.vault_credential: + res['vault_credential'] = self.reverse( + 'api:credential_detail', kwargs={'pk': obj.vault_credential.pk} + ) + except ObjectDoesNotExist: + setattr(obj, 'vault_credential', None) if self.version > 1: if isinstance(obj, UnifiedJobTemplate): res['extra_credentials'] = self.reverse( @@ -2608,15 +2650,23 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer): notifications = self.reverse('api:job_notifications_list', kwargs={'pk': obj.pk}), labels = self.reverse('api:job_label_list', kwargs={'pk': obj.pk}), )) - if obj.job_template: - res['job_template'] = self.reverse('api:job_template_detail', - kwargs={'pk': obj.job_template.pk}) + try: + if obj.job_template: + res['job_template'] = self.reverse('api:job_template_detail', + kwargs={'pk': obj.job_template.pk}) + except ObjectDoesNotExist: + setattr(obj, 'job_template', None) if (obj.can_start or True) and self.version == 1: # TODO: remove in 3.3 res['start'] = self.reverse('api:job_start', kwargs={'pk': obj.pk}) if obj.can_cancel or True: res['cancel'] = self.reverse('api:job_cancel', kwargs={'pk': obj.pk}) - if obj.project_update: - res['project_update'] = self.reverse('api:project_update_detail', kwargs={'pk': obj.project_update.pk}) + try: + if obj.project_update: + res['project_update'] = self.reverse( + 'api:project_update_detail', kwargs={'pk': obj.project_update.pk} + ) + except ObjectDoesNotExist: + pass res['create_schedule'] = self.reverse('api:job_create_schedule', kwargs={'pk': obj.pk}) res['relaunch'] = self.reverse('api:job_relaunch', kwargs={'pk': obj.pk}) return res @@ -2756,8 +2806,10 @@ class JobRelaunchSerializer(BaseSerializer): def validate(self, attrs): obj = self.context.get('obj') - if not obj.credential: - raise serializers.ValidationError(dict(credential=[_("Credential not found or deleted.")])) + if not obj.credential and not obj.vault_credential: + raise serializers.ValidationError( + dict(credential=[_("Neither credential nor vault credential provided.")]) + ) if obj.project is None: raise serializers.ValidationError(dict(errors=[_("Job Template Project is missing or undefined.")])) if obj.inventory is None or obj.inventory.pending_deletion: @@ -3820,6 +3872,7 @@ class InstanceSerializer(BaseSerializer): class InstanceGroupSerializer(BaseSerializer): + committed_capacity = serializers.SerializerMethodField() consumed_capacity = serializers.SerializerMethodField() percent_capacity_remaining = serializers.SerializerMethodField() jobs_running = serializers.SerializerMethodField() @@ -3827,7 +3880,8 @@ class InstanceGroupSerializer(BaseSerializer): class Meta: model = InstanceGroup - fields = ("id", "type", "url", "related", "name", "created", "modified", "capacity", "consumed_capacity", + fields = ("id", "type", "url", "related", "name", "created", "modified", + "capacity", "committed_capacity", "consumed_capacity", "percent_capacity_remaining", "jobs_running", "instances", "controller") def get_related(self, obj): @@ -3856,7 +3910,10 @@ class InstanceGroupSerializer(BaseSerializer): return self.context['capacity_map'] def get_consumed_capacity(self, obj): - return self.get_capacity_dict()[obj.name]['consumed_capacity'] + return self.get_capacity_dict()[obj.name]['running_capacity'] + + def get_committed_capacity(self, obj): + return self.get_capacity_dict()[obj.name]['committed_capacity'] def get_percent_capacity_remaining(self, obj): if not obj.capacity: @@ -3954,6 +4011,11 @@ class ActivityStreamSerializer(BaseSerializer): if fk == 'schedule': rel['unified_job_template'] = thisItem.unified_job_template.get_absolute_url(self.context.get('request')) + if obj.setting and obj.setting.get('category', None): + rel['setting'] = self.reverse( + 'api:setting_singleton_detail', + kwargs={'category_slug': obj.setting['category']} + ) return rel def _get_rel(self, obj, fk): @@ -4005,6 +4067,8 @@ class ActivityStreamSerializer(BaseSerializer): username = obj.actor.username, first_name = obj.actor.first_name, last_name = obj.actor.last_name) + if obj.setting: + summary_fields['setting'] = [obj.setting] return summary_fields diff --git a/awx/api/templates/api/_list_common.md b/awx/api/templates/api/_list_common.md index 706ae732a5..de58292756 100644 --- a/awx/api/templates/api/_list_common.md +++ b/awx/api/templates/api/_list_common.md @@ -1,9 +1,9 @@ The resulting data structure contains: { - "count": 99, - "next": null, - "previous": null, + "count": 99, + "next": null, + "previous": null, "results": [ ... ] @@ -60,6 +60,10 @@ _Added in AWX 1.4_ ?related__search=findme +Note: If you want to provide more than one search terms, please use multiple +search fields with the same key, like `?related__search=foo&related__search=bar`, +All search terms with the same key will be ORed together. + ## Filtering Any additional query string parameters may be used to filter the list of @@ -70,7 +74,7 @@ in the specified value should be url-encoded. For example: ?field=value%20xyz Fields may also span relations, only for fields and relationships defined in -the database: +the database: ?other__field=value diff --git a/awx/api/templates/api/sub_list_destroy_api_view.md b/awx/api/templates/api/sub_list_destroy_api_view.md new file mode 100644 index 0000000000..72654f88a0 --- /dev/null +++ b/awx/api/templates/api/sub_list_destroy_api_view.md @@ -0,0 +1,6 @@ +{% include "api/sub_list_create_api_view.md" %} + +# Delete all {{ model_verbose_name_plural }} of this {{ parent_model_verbose_name|title }}: + +Make a DELETE request to this resource to delete all {{ model_verbose_name_plural }} show in the list. +The {{ parent_model_verbose_name|title }} will not be deleted by this request. diff --git a/awx/api/views.py b/awx/api/views.py index 97a3d57400..42e38c410a 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -14,6 +14,7 @@ import logging import requests from base64 import b64encode from collections import OrderedDict, Iterable +import six # Django from django.conf import settings @@ -72,6 +73,7 @@ from awx.main.utils import ( extract_ansible_vars, decrypt_field, ) +from awx.main.utils.encryption import encrypt_value from awx.main.utils.filters import SmartFilter from awx.main.utils.insights import filter_insights_api_response @@ -1967,7 +1969,17 @@ class InventoryJobTemplateList(SubListAPIView): return qs.filter(inventory=parent) -class HostList(ListCreateAPIView): +class HostRelatedSearchMixin(object): + + @property + def related_search_fields(self): + # Edge-case handle: https://github.com/ansible/ansible-tower/issues/7712 + ret = super(HostRelatedSearchMixin, self).related_search_fields + ret.append('ansible_facts') + return ret + + +class HostList(HostRelatedSearchMixin, ListCreateAPIView): always_allow_superuser = False model = Host @@ -2004,7 +2016,7 @@ class HostAnsibleFactsDetail(RetrieveAPIView): new_in_api_v2 = True -class InventoryHostsList(SubListCreateAttachDetachAPIView): +class InventoryHostsList(HostRelatedSearchMixin, SubListCreateAttachDetachAPIView): model = Host serializer_class = HostSerializer @@ -2274,7 +2286,9 @@ class GroupPotentialChildrenList(SubListAPIView): return qs.exclude(pk__in=except_pks) -class GroupHostsList(ControlledByScmMixin, SubListCreateAttachDetachAPIView): +class GroupHostsList(HostRelatedSearchMixin, + ControlledByScmMixin, + SubListCreateAttachDetachAPIView): ''' the list of hosts directly below a group ''' model = Host @@ -2301,7 +2315,7 @@ class GroupHostsList(ControlledByScmMixin, SubListCreateAttachDetachAPIView): return super(GroupHostsList, self).create(request, *args, **kwargs) -class GroupAllHostsList(SubListAPIView): +class GroupAllHostsList(HostRelatedSearchMixin, SubListAPIView): ''' the list of all hosts below a group, even including subgroups ''' model = Host @@ -2419,6 +2433,8 @@ class InventoryScriptView(RetrieveAPIView): def retrieve(self, request, *args, **kwargs): obj = self.get_object() hostname = request.query_params.get('host', '') + hostvars = bool(request.query_params.get('hostvars', '')) + towervars = bool(request.query_params.get('towervars', '')) show_all = bool(request.query_params.get('all', '')) if hostname: hosts_q = dict(name=hostname) @@ -2607,23 +2623,25 @@ class InventorySourceNotificationTemplatesSuccessList(InventorySourceNotificatio relationship = 'notification_templates_success' -class InventorySourceHostsList(SubListAPIView): +class InventorySourceHostsList(HostRelatedSearchMixin, SubListDestroyAPIView): model = Host serializer_class = HostSerializer parent_model = InventorySource relationship = 'hosts' new_in_148 = True + check_sub_obj_permission = False capabilities_prefetch = ['inventory.admin'] -class InventorySourceGroupsList(SubListAPIView): +class InventorySourceGroupsList(SubListDestroyAPIView): model = Group serializer_class = GroupSerializer parent_model = InventorySource relationship = 'groups' new_in_148 = True + check_sub_obj_permission = False class InventorySourceUpdatesList(SubListAPIView): @@ -2918,13 +2936,8 @@ class JobTemplateSurveySpec(GenericAPIView): if not feature_enabled('surveys'): raise LicenseForbids(_('Your license does not allow ' 'adding surveys.')) - survey_spec = obj.survey_spec - for pos, field in enumerate(survey_spec.get('spec', [])): - if field.get('type') == 'password': - if 'default' in field and field['default']: - field['default'] = '$encrypted$' - return Response(survey_spec) + return Response(obj.display_survey_spec()) def post(self, request, *args, **kwargs): obj = self.get_object() @@ -2937,7 +2950,14 @@ class JobTemplateSurveySpec(GenericAPIView): if not request.user.can_access(self.model, 'change', obj, None): raise PermissionDenied() - new_spec = request.data + response = self._validate_spec_data(request.data, obj.survey_spec) + if response: + return response + obj.survey_spec = request.data + obj.save(update_fields=['survey_spec']) + return Response() + + def _validate_spec_data(self, new_spec, old_spec): if "name" not in new_spec: return Response(dict(error=_("'name' missing from survey spec.")), status=status.HTTP_400_BAD_REQUEST) if "description" not in new_spec: @@ -2949,9 +2969,9 @@ class JobTemplateSurveySpec(GenericAPIView): if len(new_spec["spec"]) < 1: return Response(dict(error=_("'spec' doesn't contain any items.")), status=status.HTTP_400_BAD_REQUEST) - idx = 0 variable_set = set() - for survey_item in new_spec["spec"]: + old_spec_dict = JobTemplate.pivot_spec(old_spec) + for idx, survey_item in enumerate(new_spec["spec"]): if not isinstance(survey_item, dict): return Response(dict(error=_("Survey question %s is not a json object.") % str(idx)), status=status.HTTP_400_BAD_REQUEST) if "type" not in survey_item: @@ -2968,21 +2988,41 @@ class JobTemplateSurveySpec(GenericAPIView): if "required" not in survey_item: return Response(dict(error=_("'required' missing from survey question %s.") % str(idx)), status=status.HTTP_400_BAD_REQUEST) - if survey_item["type"] == "password": - if survey_item.get("default") and survey_item["default"].startswith('$encrypted$'): - if not obj.survey_spec: - return Response(dict(error=_("$encrypted$ is reserved keyword and may not be used as a default for password {}.".format(str(idx)))), - status=status.HTTP_400_BAD_REQUEST) - else: - old_spec = obj.survey_spec - for old_item in old_spec['spec']: - if old_item['variable'] == survey_item['variable']: - survey_item['default'] = old_item['default'] - idx += 1 + if survey_item["type"] == "password" and "default" in survey_item: + if not isinstance(survey_item['default'], six.string_types): + return Response(dict(error=_( + "Value {question_default} for '{variable_name}' expected to be a string." + ).format( + question_default=survey_item["default"], variable_name=survey_item["variable"]) + ), status=status.HTTP_400_BAD_REQUEST) - obj.survey_spec = new_spec - obj.save(update_fields=['survey_spec']) - return Response() + if ("default" in survey_item and isinstance(survey_item['default'], six.string_types) and + survey_item['default'].startswith('$encrypted$')): + # Submission expects the existence of encrypted DB value to replace given default + if survey_item["type"] != "password": + return Response(dict(error=_( + "$encrypted$ is a reserved keyword for password question defaults, " + "survey question {question_position} is type {question_type}." + ).format( + question_position=str(idx), question_type=survey_item["type"]) + ), status=status.HTTP_400_BAD_REQUEST) + old_element = old_spec_dict.get(survey_item['variable'], {}) + encryptedish_default_exists = False + if 'default' in old_element: + old_default = old_element['default'] + if isinstance(old_default, six.string_types): + if old_default.startswith('$encrypted$'): + encryptedish_default_exists = True + elif old_default == "": # unencrypted blank string is allowed as DB value as special case + encryptedish_default_exists = True + if not encryptedish_default_exists: + return Response(dict(error=_( + "$encrypted$ is a reserved keyword, may not be used for new default in position {question_position}." + ).format(question_position=str(idx))), status=status.HTTP_400_BAD_REQUEST) + survey_item['default'] = old_element['default'] + elif survey_item["type"] == "password" and 'default' in survey_item: + # Submission provides new encrypted default + survey_item['default'] = encrypt_value(survey_item['default']) def delete(self, request, *args, **kwargs): obj = self.get_object() @@ -4121,7 +4161,7 @@ class JobEventChildrenList(SubListAPIView): view_name = _('Job Event Children List') -class JobEventHostsList(SubListAPIView): +class JobEventHostsList(HostRelatedSearchMixin, SubListAPIView): model = Host serializer_class = HostSerializer @@ -4141,7 +4181,7 @@ class BaseJobEventsList(SubListAPIView): search_fields = ('stdout',) def finalize_response(self, request, response, *args, **kwargs): - response['X-UI-Max-Events'] = settings.RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER + response['X-UI-Max-Events'] = settings.MAX_UI_JOB_EVENTS return super(BaseJobEventsList, self).finalize_response(request, response, *args, **kwargs) diff --git a/awx/conf/fields.py b/awx/conf/fields.py index 50aca441e0..b98b925447 100644 --- a/awx/conf/fields.py +++ b/awx/conf/fields.py @@ -1,6 +1,7 @@ # Python import logging import urlparse +from collections import OrderedDict # Django from django.core.validators import URLValidator @@ -139,6 +140,8 @@ class KeyValueField(DictField): ret = super(KeyValueField, self).to_internal_value(data) for value in data.values(): if not isinstance(value, six.string_types + six.integer_types + (float,)): + if isinstance(value, OrderedDict): + value = dict(value) self.fail('invalid_child', input=value) return ret diff --git a/awx/conf/registry.py b/awx/conf/registry.py index 65cb955381..23c45b078c 100644 --- a/awx/conf/registry.py +++ b/awx/conf/registry.py @@ -120,6 +120,9 @@ class SettingsRegistry(object): def is_setting_read_only(self, setting): return bool(self._registry.get(setting, {}).get('read_only', False)) + def get_setting_category(self, setting): + return self._registry.get(setting, {}).get('category_slug', None) + def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs): from rest_framework.fields import empty field_kwargs = {} diff --git a/awx/conf/serializers.py b/awx/conf/serializers.py index 46f2e93467..8f9f80cc87 100644 --- a/awx/conf/serializers.py +++ b/awx/conf/serializers.py @@ -87,8 +87,10 @@ class SettingSingletonSerializer(serializers.Serializer): if self.instance and not hasattr(self.instance, key): continue extra_kwargs = {} - # Make LICENSE read-only here; update via /api/v1/config/ only. - if key == 'LICENSE': + # Make LICENSE and AWX_ISOLATED_KEY_GENERATION read-only here; + # LICENSE is only updated via /api/v1/config/ + # AWX_ISOLATED_KEY_GENERATION is only set/unset via the setup playbook + if key in ('LICENSE', 'AWX_ISOLATED_KEY_GENERATION'): extra_kwargs['read_only'] = True field = settings_registry.get_setting_field(key, mixin_class=SettingFieldMixin, for_user=bool(category_slug == 'user'), **extra_kwargs) fields[key] = field diff --git a/awx/conf/utils.py b/awx/conf/utils.py index b780038e9f..b0d4ffb694 100755 --- a/awx/conf/utils.py +++ b/awx/conf/utils.py @@ -9,7 +9,10 @@ import shutil # RedBaron from redbaron import RedBaron, indent -__all__ = ['comment_assignments'] +# AWX +from awx.conf.registry import settings_registry + +__all__ = ['comment_assignments', 'conf_to_dict'] def comment_assignments(patterns, assignment_names, dry_run=True, backup_suffix='.old'): @@ -103,6 +106,13 @@ def comment_assignments_in_file(filename, assignment_names, dry_run=True, backup return '\n'.join(diff_lines) +def conf_to_dict(obj): + return { + 'category': settings_registry.get_setting_category(obj.key), + 'name': obj.key, + } + + if __name__ == '__main__': pattern = os.path.join(os.path.dirname(__file__), '..', 'settings', 'local_*.py') diffs = comment_assignments(pattern, ['AUTH_LDAP_ORGANIZATION_MAP']) diff --git a/awx/locale/django.pot b/awx/locale/django.pot index 3038bed7d1..fc8c1554da 100644 --- a/awx/locale/django.pot +++ b/awx/locale/django.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-08-27 19:27+0000\n" +"POT-Creation-Date: 2017-11-30 20:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -78,25 +78,30 @@ msgstr "" msgid "Loops not allowed in filters, detected on field {}." msgstr "" -#: awx/api/filters.py:297 +#: awx/api/filters.py:171 +#, python-brace-format +msgid "Invalid {field_name} id: {field_id}" +msgstr "" + +#: awx/api/filters.py:302 #, python-format msgid "cannot filter on kind %s" msgstr "" -#: awx/api/filters.py:404 +#: awx/api/filters.py:409 #, python-format msgid "cannot order by field %s" msgstr "" -#: awx/api/generics.py:515 awx/api/generics.py:577 +#: awx/api/generics.py:550 awx/api/generics.py:612 msgid "\"id\" field must be an integer." msgstr "" -#: awx/api/generics.py:574 +#: awx/api/generics.py:609 msgid "\"id\" is required to disassociate" msgstr "" -#: awx/api/generics.py:625 +#: awx/api/generics.py:660 msgid "{} 'id' field is missing." msgstr "" @@ -175,807 +180,833 @@ msgstr "" msgid "Workflow Template" msgstr "" -#: awx/api/serializers.py:697 awx/api/serializers.py:755 awx/api/views.py:4314 +#: awx/api/serializers.py:701 awx/api/serializers.py:759 awx/api/views.py:4365 #, python-format msgid "" "Standard Output too large to display (%(text_size)d bytes), only download " "supported for sizes over %(supported_size)d bytes" msgstr "" -#: awx/api/serializers.py:770 +#: awx/api/serializers.py:774 msgid "Write-only field used to change the password." msgstr "" -#: awx/api/serializers.py:772 +#: awx/api/serializers.py:776 msgid "Set if the account is managed by an external service" msgstr "" -#: awx/api/serializers.py:796 +#: awx/api/serializers.py:800 msgid "Password required for new User." msgstr "" -#: awx/api/serializers.py:882 +#: awx/api/serializers.py:886 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "" -#: awx/api/serializers.py:1046 +#: awx/api/serializers.py:1050 msgid "Organization is missing" msgstr "" -#: awx/api/serializers.py:1050 +#: awx/api/serializers.py:1054 msgid "Update options must be set to false for manual projects." msgstr "" -#: awx/api/serializers.py:1056 +#: awx/api/serializers.py:1060 msgid "Array of playbooks available within this project." msgstr "" -#: awx/api/serializers.py:1075 +#: awx/api/serializers.py:1079 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." msgstr "" -#: awx/api/serializers.py:1282 +#: awx/api/serializers.py:1201 +msgid "Smart inventories must specify host_filter" +msgstr "" + +#: awx/api/serializers.py:1303 #, python-format msgid "Invalid port specification: %s" msgstr "" -#: awx/api/serializers.py:1293 +#: awx/api/serializers.py:1314 msgid "Cannot create Host for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1315 awx/api/serializers.py:3218 -#: awx/api/serializers.py:3303 awx/main/validators.py:198 +#: awx/api/serializers.py:1336 awx/api/serializers.py:3321 +#: awx/api/serializers.py:3406 awx/main/validators.py:198 msgid "Must be valid JSON or YAML." msgstr "" -#: awx/api/serializers.py:1411 +#: awx/api/serializers.py:1432 msgid "Invalid group name." msgstr "" -#: awx/api/serializers.py:1416 +#: awx/api/serializers.py:1437 msgid "Cannot create Group for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1488 +#: awx/api/serializers.py:1509 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" -#: awx/api/serializers.py:1534 +#: awx/api/serializers.py:1555 msgid "`{}` is a prohibited environment variable" msgstr "" -#: awx/api/serializers.py:1545 +#: awx/api/serializers.py:1566 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "" -#: awx/api/serializers.py:1551 +#: awx/api/serializers.py:1572 msgid "Must provide an inventory." msgstr "" -#: awx/api/serializers.py:1555 +#: awx/api/serializers.py:1576 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" -#: awx/api/serializers.py:1557 +#: awx/api/serializers.py:1578 msgid "'source_script' doesn't exist." msgstr "" -#: awx/api/serializers.py:1581 +#: awx/api/serializers.py:1602 msgid "Automatic group relationship, will be removed in 3.3" msgstr "" -#: awx/api/serializers.py:1658 +#: awx/api/serializers.py:1679 msgid "Cannot use manual project for SCM-based inventory." msgstr "" -#: awx/api/serializers.py:1664 +#: awx/api/serializers.py:1685 msgid "" "Manual inventory sources are created automatically when a group is created " "in the v1 API." msgstr "" -#: awx/api/serializers.py:1669 +#: awx/api/serializers.py:1690 msgid "Setting not compatible with existing schedules." msgstr "" -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1695 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1688 +#: awx/api/serializers.py:1709 #, python-format msgid "Cannot set %s if not SCM type." msgstr "" -#: awx/api/serializers.py:1929 +#: awx/api/serializers.py:1950 msgid "Modifications not allowed for managed credential types" msgstr "" -#: awx/api/serializers.py:1934 +#: awx/api/serializers.py:1955 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "" -#: awx/api/serializers.py:1940 +#: awx/api/serializers.py:1961 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1967 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "" -#: awx/api/serializers.py:2119 +#: awx/api/serializers.py:2140 #, python-format msgid "\"%s\" is not a valid choice" msgstr "" -#: awx/api/serializers.py:2138 +#: awx/api/serializers.py:2159 #, python-format msgid "'%s' is not a valid field for %s" msgstr "" -#: awx/api/serializers.py:2150 +#: awx/api/serializers.py:2180 +msgid "" +"You cannot change the credential type of the credential, as it may break the " +"functionality of the resources using it." +msgstr "" + +#: awx/api/serializers.py:2191 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:2155 +#: awx/api/serializers.py:2196 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:2160 +#: awx/api/serializers.py:2201 msgid "" "Inherit permissions from organization roles. If provided on creation, do not " "give either user or team." msgstr "" -#: awx/api/serializers.py:2176 +#: awx/api/serializers.py:2217 msgid "Missing 'user', 'team', or 'organization'." msgstr "" -#: awx/api/serializers.py:2216 +#: awx/api/serializers.py:2257 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "" -#: awx/api/serializers.py:2378 +#: awx/api/serializers.py:2424 msgid "You must provide a cloud credential." msgstr "" -#: awx/api/serializers.py:2379 +#: awx/api/serializers.py:2425 msgid "You must provide a network credential." msgstr "" -#: awx/api/serializers.py:2395 +#: awx/api/serializers.py:2441 msgid "This field is required." msgstr "" -#: awx/api/serializers.py:2397 awx/api/serializers.py:2399 +#: awx/api/serializers.py:2443 awx/api/serializers.py:2445 msgid "Playbook not found for project." msgstr "" -#: awx/api/serializers.py:2401 +#: awx/api/serializers.py:2447 msgid "Must select playbook for project." msgstr "" -#: awx/api/serializers.py:2476 +#: awx/api/serializers.py:2522 msgid "Must either set a default value or ask to prompt on launch." msgstr "" -#: awx/api/serializers.py:2478 awx/main/models/jobs.py:325 +#: awx/api/serializers.py:2524 awx/main/models/jobs.py:326 msgid "Job types 'run' and 'check' must have assigned a project." msgstr "" -#: awx/api/serializers.py:2549 +#: awx/api/serializers.py:2611 msgid "Invalid job template." msgstr "" -#: awx/api/serializers.py:2630 -msgid "Credential not found or deleted." +#: awx/api/serializers.py:2708 +msgid "Neither credential nor vault credential provided." msgstr "" -#: awx/api/serializers.py:2632 +#: awx/api/serializers.py:2711 msgid "Job Template Project is missing or undefined." msgstr "" -#: awx/api/serializers.py:2634 +#: awx/api/serializers.py:2713 msgid "Job Template Inventory is missing or undefined." msgstr "" -#: awx/api/serializers.py:2921 +#: awx/api/serializers.py:2782 awx/main/tasks.py:2186 +msgid "{} are prohibited from use in ad hoc commands." +msgstr "" + +#: awx/api/serializers.py:3008 #, python-format msgid "%(job_type)s is not a valid job type. The choices are %(choices)s." msgstr "" -#: awx/api/serializers.py:2926 +#: awx/api/serializers.py:3013 msgid "Workflow job template is missing during creation." msgstr "" -#: awx/api/serializers.py:2931 +#: awx/api/serializers.py:3018 #, python-format msgid "Cannot nest a %s inside a WorkflowJobTemplate" msgstr "" -#: awx/api/serializers.py:3188 +#: awx/api/serializers.py:3291 #, python-format msgid "Job Template '%s' is missing or undefined." msgstr "" -#: awx/api/serializers.py:3191 +#: awx/api/serializers.py:3294 msgid "The inventory associated with this Job Template is being deleted." msgstr "" -#: awx/api/serializers.py:3232 awx/api/views.py:2991 +#: awx/api/serializers.py:3335 awx/api/views.py:3023 #, python-format msgid "Cannot assign multiple %s credentials." msgstr "" -#: awx/api/serializers.py:3234 awx/api/views.py:2994 +#: awx/api/serializers.py:3337 awx/api/views.py:3026 msgid "Extra credentials must be network or cloud." msgstr "" -#: awx/api/serializers.py:3371 +#: awx/api/serializers.py:3474 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" -#: awx/api/serializers.py:3394 +#: awx/api/serializers.py:3497 msgid "No values specified for field '{}'" msgstr "" -#: awx/api/serializers.py:3399 +#: awx/api/serializers.py:3502 msgid "Missing required fields for Notification Configuration: {}." msgstr "" -#: awx/api/serializers.py:3402 +#: awx/api/serializers.py:3505 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "" -#: awx/api/serializers.py:3455 +#: awx/api/serializers.py:3558 msgid "Inventory Source must be a cloud resource." msgstr "" -#: awx/api/serializers.py:3457 +#: awx/api/serializers.py:3560 msgid "Manual Project cannot have a schedule set." msgstr "" -#: awx/api/serializers.py:3460 +#: awx/api/serializers.py:3563 msgid "" "Inventory sources with `update_on_project_update` cannot be scheduled. " "Schedule its source project `{}` instead." msgstr "" -#: awx/api/serializers.py:3479 +#: awx/api/serializers.py:3582 msgid "Projects and inventory updates cannot accept extra variables." msgstr "" -#: awx/api/serializers.py:3501 +#: awx/api/serializers.py:3604 msgid "DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ" msgstr "" -#: awx/api/serializers.py:3503 +#: awx/api/serializers.py:3606 msgid "Multiple DTSTART is not supported." msgstr "" -#: awx/api/serializers.py:3505 +#: awx/api/serializers.py:3608 msgid "RRULE require in rrule." msgstr "" -#: awx/api/serializers.py:3507 +#: awx/api/serializers.py:3610 msgid "Multiple RRULE is not supported." msgstr "" -#: awx/api/serializers.py:3509 +#: awx/api/serializers.py:3612 msgid "INTERVAL required in rrule." msgstr "" -#: awx/api/serializers.py:3511 +#: awx/api/serializers.py:3614 msgid "TZID is not supported." msgstr "" -#: awx/api/serializers.py:3513 +#: awx/api/serializers.py:3616 msgid "SECONDLY is not supported." msgstr "" -#: awx/api/serializers.py:3515 +#: awx/api/serializers.py:3618 msgid "Multiple BYMONTHDAYs not supported." msgstr "" -#: awx/api/serializers.py:3517 +#: awx/api/serializers.py:3620 msgid "Multiple BYMONTHs not supported." msgstr "" -#: awx/api/serializers.py:3519 +#: awx/api/serializers.py:3622 msgid "BYDAY with numeric prefix not supported." msgstr "" -#: awx/api/serializers.py:3521 +#: awx/api/serializers.py:3624 msgid "BYYEARDAY not supported." msgstr "" -#: awx/api/serializers.py:3523 +#: awx/api/serializers.py:3626 msgid "BYWEEKNO not supported." msgstr "" -#: awx/api/serializers.py:3527 +#: awx/api/serializers.py:3630 msgid "COUNT > 999 is unsupported." msgstr "" -#: awx/api/serializers.py:3531 +#: awx/api/serializers.py:3634 msgid "rrule parsing failed validation." msgstr "" -#: awx/api/serializers.py:3631 +#: awx/api/serializers.py:3760 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" msgstr "" -#: awx/api/serializers.py:3633 +#: awx/api/serializers.py:3762 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." msgstr "" -#: awx/api/serializers.py:3636 +#: awx/api/serializers.py:3765 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated " "with." msgstr "" -#: awx/api/serializers.py:3639 +#: awx/api/serializers.py:3768 msgid "The action taken with respect to the given object(s)." msgstr "" -#: awx/api/serializers.py:3749 +#: awx/api/serializers.py:3885 msgid "Unable to login with provided credentials." msgstr "" -#: awx/api/serializers.py:3751 +#: awx/api/serializers.py:3887 msgid "Must include \"username\" and \"password\"." msgstr "" -#: awx/api/views.py:106 +#: awx/api/views.py:108 msgid "Your license does not allow use of the activity stream." msgstr "" -#: awx/api/views.py:116 +#: awx/api/views.py:118 msgid "Your license does not permit use of system tracking." msgstr "" -#: awx/api/views.py:126 +#: awx/api/views.py:128 msgid "Your license does not allow use of workflows." msgstr "" -#: awx/api/views.py:140 +#: awx/api/views.py:142 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" -#: awx/api/views.py:144 +#: awx/api/views.py:146 msgid "Cannot delete running job resource." msgstr "" -#: awx/api/views.py:153 awx/templates/rest_framework/api.html:28 +#: awx/api/views.py:155 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "" -#: awx/api/views.py:162 awx/templates/rest_framework/api.html:4 +#: awx/api/views.py:164 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "" -#: awx/api/views.py:224 +#: awx/api/views.py:226 msgid "Version 1" msgstr "" -#: awx/api/views.py:228 +#: awx/api/views.py:230 msgid "Version 2" msgstr "" -#: awx/api/views.py:239 +#: awx/api/views.py:241 msgid "Ping" msgstr "" -#: awx/api/views.py:270 awx/conf/apps.py:12 +#: awx/api/views.py:272 awx/conf/apps.py:12 msgid "Configuration" msgstr "" -#: awx/api/views.py:323 +#: awx/api/views.py:325 msgid "Invalid license data" msgstr "" -#: awx/api/views.py:325 +#: awx/api/views.py:327 msgid "Missing 'eula_accepted' property" msgstr "" -#: awx/api/views.py:329 +#: awx/api/views.py:331 msgid "'eula_accepted' value is invalid" msgstr "" -#: awx/api/views.py:332 +#: awx/api/views.py:334 msgid "'eula_accepted' must be True" msgstr "" -#: awx/api/views.py:339 +#: awx/api/views.py:341 msgid "Invalid JSON" msgstr "" -#: awx/api/views.py:347 +#: awx/api/views.py:349 msgid "Invalid License" msgstr "" -#: awx/api/views.py:357 +#: awx/api/views.py:359 msgid "Invalid license" msgstr "" -#: awx/api/views.py:365 +#: awx/api/views.py:367 #, python-format msgid "Failed to remove license (%s)" msgstr "" -#: awx/api/views.py:370 +#: awx/api/views.py:372 msgid "Dashboard" msgstr "" -#: awx/api/views.py:469 +#: awx/api/views.py:471 msgid "Dashboard Jobs Graphs" msgstr "" -#: awx/api/views.py:505 +#: awx/api/views.py:507 #, python-format msgid "Unknown period \"%s\"" msgstr "" -#: awx/api/views.py:519 +#: awx/api/views.py:521 msgid "Instances" msgstr "" -#: awx/api/views.py:527 +#: awx/api/views.py:529 msgid "Instance Detail" msgstr "" -#: awx/api/views.py:535 +#: awx/api/views.py:537 msgid "Instance Running Jobs" msgstr "" -#: awx/api/views.py:550 +#: awx/api/views.py:552 msgid "Instance's Instance Groups" msgstr "" -#: awx/api/views.py:560 +#: awx/api/views.py:562 msgid "Instance Groups" msgstr "" -#: awx/api/views.py:568 +#: awx/api/views.py:570 msgid "Instance Group Detail" msgstr "" -#: awx/api/views.py:576 +#: awx/api/views.py:578 msgid "Instance Group Running Jobs" msgstr "" -#: awx/api/views.py:586 +#: awx/api/views.py:588 msgid "Instance Group's Instances" msgstr "" -#: awx/api/views.py:596 +#: awx/api/views.py:598 msgid "Schedules" msgstr "" -#: awx/api/views.py:615 +#: awx/api/views.py:617 msgid "Schedule Jobs List" msgstr "" -#: awx/api/views.py:841 +#: awx/api/views.py:843 msgid "Your license only permits a single organization to exist." msgstr "" -#: awx/api/views.py:1077 awx/api/views.py:4615 +#: awx/api/views.py:1079 awx/api/views.py:4666 msgid "You cannot assign an Organization role as a child role for a Team." msgstr "" -#: awx/api/views.py:1081 awx/api/views.py:4629 +#: awx/api/views.py:1083 awx/api/views.py:4680 msgid "You cannot grant system-level permissions to a team." msgstr "" -#: awx/api/views.py:1088 awx/api/views.py:4621 +#: awx/api/views.py:1090 awx/api/views.py:4672 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" msgstr "" -#: awx/api/views.py:1178 +#: awx/api/views.py:1180 msgid "Cannot delete project." msgstr "" -#: awx/api/views.py:1213 +#: awx/api/views.py:1215 msgid "Project Schedules" msgstr "" -#: awx/api/views.py:1225 +#: awx/api/views.py:1227 msgid "Project SCM Inventory Sources" msgstr "" -#: awx/api/views.py:1352 +#: awx/api/views.py:1354 msgid "Project Update SCM Inventory Updates" msgstr "" -#: awx/api/views.py:1407 +#: awx/api/views.py:1409 msgid "Me" msgstr "" -#: awx/api/views.py:1451 awx/api/views.py:4572 +#: awx/api/views.py:1453 awx/api/views.py:4623 msgid "You may not perform any action with your own admin_role." msgstr "" -#: awx/api/views.py:1457 awx/api/views.py:4576 +#: awx/api/views.py:1459 awx/api/views.py:4627 msgid "You may not change the membership of a users admin_role" msgstr "" -#: awx/api/views.py:1462 awx/api/views.py:4581 +#: awx/api/views.py:1464 awx/api/views.py:4632 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" msgstr "" -#: awx/api/views.py:1466 awx/api/views.py:4585 +#: awx/api/views.py:1468 awx/api/views.py:4636 msgid "You cannot grant private credential access to another user" msgstr "" -#: awx/api/views.py:1564 +#: awx/api/views.py:1566 #, python-format msgid "Cannot change %s." msgstr "" -#: awx/api/views.py:1570 +#: awx/api/views.py:1572 msgid "Cannot delete user." msgstr "" -#: awx/api/views.py:1599 +#: awx/api/views.py:1601 msgid "Deletion not allowed for managed credential types" msgstr "" -#: awx/api/views.py:1601 +#: awx/api/views.py:1603 msgid "Credential types that are in use cannot be deleted" msgstr "" -#: awx/api/views.py:1779 +#: awx/api/views.py:1781 msgid "Cannot delete inventory script." msgstr "" -#: awx/api/views.py:1864 +#: awx/api/views.py:1866 +#, python-brace-format msgid "{0}" msgstr "" -#: awx/api/views.py:2089 +#: awx/api/views.py:2101 msgid "Fact not found." msgstr "" -#: awx/api/views.py:2113 +#: awx/api/views.py:2125 msgid "SSLError while trying to connect to {}" msgstr "" -#: awx/api/views.py:2115 +#: awx/api/views.py:2127 msgid "Request to {} timed out." msgstr "" -#: awx/api/views.py:2117 +#: awx/api/views.py:2129 msgid "Unkown exception {} while trying to GET {}" msgstr "" -#: awx/api/views.py:2120 +#: awx/api/views.py:2132 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "" -#: awx/api/views.py:2122 +#: awx/api/views.py:2134 msgid "" "Failed to gather reports and maintenance plans from Insights API at URL {}. " "Server responded with {} status code and message {}" msgstr "" -#: awx/api/views.py:2128 +#: awx/api/views.py:2140 msgid "Expected JSON response from Insights but instead got {}" msgstr "" -#: awx/api/views.py:2135 +#: awx/api/views.py:2147 msgid "This host is not recognized as an Insights host." msgstr "" -#: awx/api/views.py:2140 +#: awx/api/views.py:2152 msgid "The Insights Credential for \"{}\" was not found." msgstr "" -#: awx/api/views.py:2209 +#: awx/api/views.py:2221 msgid "Cyclical Group association." msgstr "" -#: awx/api/views.py:2482 +#: awx/api/views.py:2499 msgid "Inventory Source List" msgstr "" -#: awx/api/views.py:2495 +#: awx/api/views.py:2512 msgid "Inventory Sources Update" msgstr "" -#: awx/api/views.py:2525 +#: awx/api/views.py:2542 msgid "Could not start because `can_update` returned False" msgstr "" -#: awx/api/views.py:2533 +#: awx/api/views.py:2550 msgid "No inventory sources to update." msgstr "" -#: awx/api/views.py:2565 +#: awx/api/views.py:2582 msgid "Cannot delete inventory source." msgstr "" -#: awx/api/views.py:2573 +#: awx/api/views.py:2590 msgid "Inventory Source Schedules" msgstr "" -#: awx/api/views.py:2603 +#: awx/api/views.py:2620 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" -#: awx/api/views.py:2826 +#: awx/api/views.py:2851 msgid "Job Template Schedules" msgstr "" -#: awx/api/views.py:2846 awx/api/views.py:2862 +#: awx/api/views.py:2871 awx/api/views.py:2882 msgid "Your license does not allow adding surveys." msgstr "" -#: awx/api/views.py:2869 +#: awx/api/views.py:2889 msgid "'name' missing from survey spec." msgstr "" -#: awx/api/views.py:2871 +#: awx/api/views.py:2891 msgid "'description' missing from survey spec." msgstr "" -#: awx/api/views.py:2873 +#: awx/api/views.py:2893 msgid "'spec' missing from survey spec." msgstr "" -#: awx/api/views.py:2875 +#: awx/api/views.py:2895 msgid "'spec' must be a list of items." msgstr "" -#: awx/api/views.py:2877 +#: awx/api/views.py:2897 msgid "'spec' doesn't contain any items." msgstr "" -#: awx/api/views.py:2883 +#: awx/api/views.py:2903 #, python-format msgid "Survey question %s is not a json object." msgstr "" -#: awx/api/views.py:2885 +#: awx/api/views.py:2905 #, python-format msgid "'type' missing from survey question %s." msgstr "" -#: awx/api/views.py:2887 +#: awx/api/views.py:2907 #, python-format msgid "'question_name' missing from survey question %s." msgstr "" -#: awx/api/views.py:2889 +#: awx/api/views.py:2909 #, python-format msgid "'variable' missing from survey question %s." msgstr "" -#: awx/api/views.py:2891 +#: awx/api/views.py:2911 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "" -#: awx/api/views.py:2896 +#: awx/api/views.py:2916 #, python-format msgid "'required' missing from survey question %s." msgstr "" -#: awx/api/views.py:2901 -msgid "" -"$encrypted$ is reserved keyword and may not be used as a default for " -"password {}." +#: awx/api/views.py:2921 +#, python-brace-format +msgid "Value {question_default} for '{variable_name}' expected to be a string." msgstr "" -#: awx/api/views.py:3017 +#: awx/api/views.py:2928 +#, python-brace-format +msgid "" +"$encrypted$ is reserved keyword for password questions and may not be used " +"as a default for '{variable_name}' in survey question {question_position}." +msgstr "" + +#: awx/api/views.py:3049 msgid "Maximum number of labels for {} reached." msgstr "" -#: awx/api/views.py:3136 +#: awx/api/views.py:3170 msgid "No matching host could be found!" msgstr "" -#: awx/api/views.py:3139 +#: awx/api/views.py:3173 msgid "Multiple hosts matched the request!" msgstr "" -#: awx/api/views.py:3144 +#: awx/api/views.py:3178 msgid "Cannot start automatically, user input required!" msgstr "" -#: awx/api/views.py:3151 +#: awx/api/views.py:3185 msgid "Host callback job already pending." msgstr "" -#: awx/api/views.py:3164 +#: awx/api/views.py:3199 msgid "Error starting job!" msgstr "" -#: awx/api/views.py:3271 +#: awx/api/views.py:3306 +#, python-brace-format msgid "Cannot associate {0} when {1} have been associated." msgstr "" -#: awx/api/views.py:3296 +#: awx/api/views.py:3331 msgid "Multiple parent relationship not allowed." msgstr "" -#: awx/api/views.py:3301 +#: awx/api/views.py:3336 msgid "Cycle detected." msgstr "" -#: awx/api/views.py:3505 +#: awx/api/views.py:3540 msgid "Workflow Job Template Schedules" msgstr "" -#: awx/api/views.py:3650 awx/api/views.py:4217 +#: awx/api/views.py:3685 awx/api/views.py:4268 msgid "Superuser privileges needed." msgstr "" -#: awx/api/views.py:3682 +#: awx/api/views.py:3717 msgid "System Job Template Schedules" msgstr "" -#: awx/api/views.py:3891 +#: awx/api/views.py:3780 +msgid "POST not allowed for Job launching in version 2 of the api" +msgstr "" + +#: awx/api/views.py:3942 msgid "Job Host Summaries List" msgstr "" -#: awx/api/views.py:3938 +#: awx/api/views.py:3989 msgid "Job Event Children List" msgstr "" -#: awx/api/views.py:3947 +#: awx/api/views.py:3998 msgid "Job Event Hosts List" msgstr "" -#: awx/api/views.py:3957 +#: awx/api/views.py:4008 msgid "Job Events List" msgstr "" -#: awx/api/views.py:4171 +#: awx/api/views.py:4222 msgid "Ad Hoc Command Events List" msgstr "" -#: awx/api/views.py:4386 +#: awx/api/views.py:4437 msgid "Error generating stdout download file: {}" msgstr "" -#: awx/api/views.py:4399 +#: awx/api/views.py:4450 #, python-format msgid "Error generating stdout download file: %s" msgstr "" -#: awx/api/views.py:4444 +#: awx/api/views.py:4495 msgid "Delete not allowed while there are pending notifications" msgstr "" -#: awx/api/views.py:4451 +#: awx/api/views.py:4502 msgid "Notification Template Test" msgstr "" @@ -1131,15 +1162,16 @@ msgstr "" msgid "User" msgstr "" -#: awx/conf/fields.py:62 +#: awx/conf/fields.py:63 msgid "Enter a valid URL" msgstr "" -#: awx/conf/fields.py:94 +#: awx/conf/fields.py:95 +#, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "" -#: awx/conf/license.py:19 +#: awx/conf/license.py:22 msgid "Your Tower license does not allow that." msgstr "" @@ -1155,7 +1187,11 @@ msgstr "" msgid "Skip commenting out settings in files." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:61 +#: awx/conf/management/commands/migrate_to_database_settings.py:62 +msgid "Skip migrating and only comment out settings in files." +msgstr "" + +#: awx/conf/management/commands/migrate_to_database_settings.py:68 msgid "Backup existing settings files with this suffix." msgstr "" @@ -1179,7 +1215,7 @@ msgstr "" msgid "User-Defaults" msgstr "" -#: awx/conf/registry.py:151 +#: awx/conf/registry.py:154 msgid "This value has been set manually in a settings file." msgstr "" @@ -1251,93 +1287,91 @@ msgstr "" msgid "Logging Connectivity Test" msgstr "" -#: awx/main/access.py:224 +#: awx/main/access.py:44 +msgid "Resource is being used by running jobs." +msgstr "" + +#: awx/main/access.py:237 #, python-format msgid "Bad data found in related field %s." msgstr "" -#: awx/main/access.py:268 +#: awx/main/access.py:281 msgid "License is missing." msgstr "" -#: awx/main/access.py:270 +#: awx/main/access.py:283 msgid "License has expired." msgstr "" -#: awx/main/access.py:278 +#: awx/main/access.py:291 #, python-format msgid "License count of %s instances has been reached." msgstr "" -#: awx/main/access.py:280 +#: awx/main/access.py:293 #, python-format msgid "License count of %s instances has been exceeded." msgstr "" -#: awx/main/access.py:282 +#: awx/main/access.py:295 msgid "Host count exceeds available instances." msgstr "" -#: awx/main/access.py:286 +#: awx/main/access.py:299 #, python-format msgid "Feature %s is not enabled in the active license." msgstr "" -#: awx/main/access.py:288 +#: awx/main/access.py:301 msgid "Features not found in active license." msgstr "" -#: awx/main/access.py:534 awx/main/access.py:619 awx/main/access.py:748 -#: awx/main/access.py:801 awx/main/access.py:1063 awx/main/access.py:1263 -#: awx/main/access.py:1725 -msgid "Resource is being used by running jobs" -msgstr "" - -#: awx/main/access.py:675 +#: awx/main/access.py:707 msgid "Unable to change inventory on a host." msgstr "" -#: awx/main/access.py:692 awx/main/access.py:737 +#: awx/main/access.py:724 awx/main/access.py:769 msgid "Cannot associate two items from different inventories." msgstr "" -#: awx/main/access.py:725 +#: awx/main/access.py:757 msgid "Unable to change inventory on a group." msgstr "" -#: awx/main/access.py:983 +#: awx/main/access.py:1017 msgid "Unable to change organization on a team." msgstr "" -#: awx/main/access.py:996 +#: awx/main/access.py:1030 msgid "The {} role cannot be assigned to a team" msgstr "" -#: awx/main/access.py:998 +#: awx/main/access.py:1032 msgid "The admin_role for a User cannot be assigned to a team" msgstr "" -#: awx/main/access.py:1443 +#: awx/main/access.py:1479 msgid "Job has been orphaned from its job template." msgstr "" -#: awx/main/access.py:1445 +#: awx/main/access.py:1481 msgid "You do not have execute permission to related job template." msgstr "" -#: awx/main/access.py:1448 +#: awx/main/access.py:1484 msgid "Job was launched with prompted fields." msgstr "" -#: awx/main/access.py:1450 +#: awx/main/access.py:1486 msgid " Organization level permissions required." msgstr "" -#: awx/main/access.py:1452 +#: awx/main/access.py:1488 msgid " You do not have permission to related resources." msgstr "" -#: awx/main/access.py:1798 +#: awx/main/access.py:1833 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." @@ -1781,149 +1815,149 @@ msgstr "" msgid "\n" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Sudo" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Su" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pbrun" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pfexec" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "DZDO" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pmrun" msgstr "" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Runas" msgstr "" -#: awx/main/fields.py:56 +#: awx/main/fields.py:57 #, python-format msgid "'%s' is not one of ['%s']" msgstr "" -#: awx/main/fields.py:531 +#: awx/main/fields.py:533 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "" -#: awx/main/fields.py:547 +#: awx/main/fields.py:549 #, python-format msgid "required for %s" msgstr "" -#: awx/main/fields.py:571 +#: awx/main/fields.py:573 msgid "must be set when SSH key is encrypted." msgstr "" -#: awx/main/fields.py:577 +#: awx/main/fields.py:579 msgid "should not be set when SSH key is not encrypted." msgstr "" -#: awx/main/fields.py:635 +#: awx/main/fields.py:637 msgid "'dependencies' is not supported for custom credentials." msgstr "" -#: awx/main/fields.py:649 +#: awx/main/fields.py:651 msgid "\"tower\" is a reserved field name" msgstr "" -#: awx/main/fields.py:656 +#: awx/main/fields.py:658 #, python-format msgid "field IDs must be unique (%s)" msgstr "" -#: awx/main/fields.py:669 +#: awx/main/fields.py:671 #, python-format msgid "%s not allowed for %s type (%s)" msgstr "" -#: awx/main/fields.py:753 +#: awx/main/fields.py:755 #, python-format msgid "%s uses an undefined field (%s)" msgstr "" -#: awx/main/middleware.py:121 +#: awx/main/middleware.py:157 msgid "Formats of all available named urls" msgstr "" -#: awx/main/middleware.py:122 +#: awx/main/middleware.py:158 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." msgstr "" -#: awx/main/middleware.py:124 awx/main/middleware.py:134 +#: awx/main/middleware.py:160 awx/main/middleware.py:170 msgid "Named URL" msgstr "" -#: awx/main/middleware.py:131 +#: awx/main/middleware.py:167 msgid "List of all named url graph nodes." msgstr "" -#: awx/main/middleware.py:132 +#: awx/main/middleware.py:168 msgid "" "Read-only list of key-value pairs that exposes named URL graph topology. Use " "this list to programmatically generate named URLs for resources" msgstr "" -#: awx/main/migrations/_reencrypt.py:23 awx/main/models/notifications.py:33 +#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:33 msgid "Email" msgstr "" -#: awx/main/migrations/_reencrypt.py:24 awx/main/models/notifications.py:34 +#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:34 msgid "Slack" msgstr "" -#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:35 +#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:35 msgid "Twilio" msgstr "" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:36 +#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:36 msgid "Pagerduty" msgstr "" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:37 +#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:37 msgid "HipChat" msgstr "" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:38 +#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:38 msgid "Webhook" msgstr "" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:39 +#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:39 msgid "IRC" msgstr "" -#: awx/main/models/activity_stream.py:24 +#: awx/main/models/activity_stream.py:25 msgid "Entity Created" msgstr "" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:26 msgid "Entity Updated" msgstr "" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:27 msgid "Entity Deleted" msgstr "" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:28 msgid "Entity Associated with another Entity" msgstr "" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:29 msgid "Entity was Disassociated with another Entity" msgstr "" @@ -1949,43 +1983,43 @@ msgstr "" msgid "No argument passed to %s module." msgstr "" -#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:904 +#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:911 msgid "Host Failed" msgstr "" -#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:905 +#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:912 msgid "Host OK" msgstr "" -#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:908 +#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:915 msgid "Host Unreachable" msgstr "" -#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:907 +#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:914 msgid "Host Skipped" msgstr "" -#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:935 +#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:942 msgid "Debug" msgstr "" -#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:936 +#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:943 msgid "Verbose" msgstr "" -#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:937 +#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:944 msgid "Deprecated" msgstr "" -#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:938 +#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:945 msgid "Warning" msgstr "" -#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:939 +#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:946 msgid "System Warning" msgstr "" -#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:940 +#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:947 #: awx/main/models/unified_jobs.py:64 msgid "Error" msgstr "" @@ -2004,162 +2038,167 @@ msgstr "" msgid "Scan" msgstr "" -#: awx/main/models/credential.py:82 +#: awx/main/models/credential.py:86 msgid "Host" msgstr "" -#: awx/main/models/credential.py:83 +#: awx/main/models/credential.py:87 msgid "The hostname or IP address to use." msgstr "" -#: awx/main/models/credential.py:89 +#: awx/main/models/credential.py:93 msgid "Username" msgstr "" -#: awx/main/models/credential.py:90 +#: awx/main/models/credential.py:94 msgid "Username for this credential." msgstr "" -#: awx/main/models/credential.py:96 +#: awx/main/models/credential.py:100 msgid "Password" msgstr "" -#: awx/main/models/credential.py:97 +#: awx/main/models/credential.py:101 msgid "" "Password for this credential (or \"ASK\" to prompt the user for machine " "credentials)." msgstr "" -#: awx/main/models/credential.py:104 +#: awx/main/models/credential.py:108 msgid "Security Token" msgstr "" -#: awx/main/models/credential.py:105 +#: awx/main/models/credential.py:109 msgid "Security Token for this credential" msgstr "" -#: awx/main/models/credential.py:111 +#: awx/main/models/credential.py:115 msgid "Project" msgstr "" -#: awx/main/models/credential.py:112 +#: awx/main/models/credential.py:116 msgid "The identifier for the project." msgstr "" -#: awx/main/models/credential.py:118 +#: awx/main/models/credential.py:122 msgid "Domain" msgstr "" -#: awx/main/models/credential.py:119 +#: awx/main/models/credential.py:123 msgid "The identifier for the domain." msgstr "" -#: awx/main/models/credential.py:124 +#: awx/main/models/credential.py:128 msgid "SSH private key" msgstr "" -#: awx/main/models/credential.py:125 +#: awx/main/models/credential.py:129 msgid "RSA or DSA private key to be used instead of password." msgstr "" -#: awx/main/models/credential.py:131 +#: awx/main/models/credential.py:135 msgid "SSH key unlock" msgstr "" -#: awx/main/models/credential.py:132 +#: awx/main/models/credential.py:136 msgid "" "Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " "user for machine credentials)." msgstr "" -#: awx/main/models/credential.py:139 +#: awx/main/models/credential.py:143 msgid "None" msgstr "" -#: awx/main/models/credential.py:140 +#: awx/main/models/credential.py:144 msgid "Privilege escalation method." msgstr "" -#: awx/main/models/credential.py:146 +#: awx/main/models/credential.py:150 msgid "Privilege escalation username." msgstr "" -#: awx/main/models/credential.py:152 +#: awx/main/models/credential.py:156 msgid "Password for privilege escalation method." msgstr "" -#: awx/main/models/credential.py:158 +#: awx/main/models/credential.py:162 msgid "Vault password (or \"ASK\" to prompt the user)." msgstr "" -#: awx/main/models/credential.py:162 +#: awx/main/models/credential.py:166 msgid "Whether to use the authorize mechanism." msgstr "" -#: awx/main/models/credential.py:168 +#: awx/main/models/credential.py:172 msgid "Password used by the authorize mechanism." msgstr "" -#: awx/main/models/credential.py:174 +#: awx/main/models/credential.py:178 msgid "Client Id or Application Id for the credential" msgstr "" -#: awx/main/models/credential.py:180 +#: awx/main/models/credential.py:184 msgid "Secret Token for this credential" msgstr "" -#: awx/main/models/credential.py:186 +#: awx/main/models/credential.py:190 msgid "Subscription identifier for this credential" msgstr "" -#: awx/main/models/credential.py:192 +#: awx/main/models/credential.py:196 msgid "Tenant identifier for this credential" msgstr "" -#: awx/main/models/credential.py:216 +#: awx/main/models/credential.py:220 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." msgstr "" -#: awx/main/models/credential.py:230 awx/main/models/credential.py:416 +#: awx/main/models/credential.py:234 awx/main/models/credential.py:420 msgid "" "Enter inputs using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example " "syntax." msgstr "" -#: awx/main/models/credential.py:397 +#: awx/main/models/credential.py:401 msgid "Machine" msgstr "" -#: awx/main/models/credential.py:398 +#: awx/main/models/credential.py:402 msgid "Vault" msgstr "" -#: awx/main/models/credential.py:399 +#: awx/main/models/credential.py:403 msgid "Network" msgstr "" -#: awx/main/models/credential.py:400 +#: awx/main/models/credential.py:404 msgid "Source Control" msgstr "" -#: awx/main/models/credential.py:401 +#: awx/main/models/credential.py:405 msgid "Cloud" msgstr "" -#: awx/main/models/credential.py:402 +#: awx/main/models/credential.py:406 msgid "Insights" msgstr "" -#: awx/main/models/credential.py:423 +#: awx/main/models/credential.py:427 msgid "" "Enter injectors using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example " "syntax." msgstr "" +#: awx/main/models/credential.py:478 +#, python-format +msgid "adding %s credential type" +msgstr "" + #: awx/main/models/fact.py:25 msgid "Host for the facts that the fact scan captured." msgstr "" @@ -2174,11 +2213,11 @@ msgid "" "host." msgstr "" -#: awx/main/models/ha.py:76 +#: awx/main/models/ha.py:78 msgid "Instances that are members of this InstanceGroup" msgstr "" -#: awx/main/models/ha.py:81 +#: awx/main/models/ha.py:83 msgid "Instance Group to remotely control this group." msgstr "" @@ -2361,322 +2400,332 @@ msgid "Google Compute Engine" msgstr "" #: awx/main/models/inventory.py:870 -msgid "Microsoft Azure Classic (deprecated)" -msgstr "" - -#: awx/main/models/inventory.py:871 msgid "Microsoft Azure Resource Manager" msgstr "" -#: awx/main/models/inventory.py:872 +#: awx/main/models/inventory.py:871 msgid "VMware vCenter" msgstr "" -#: awx/main/models/inventory.py:873 +#: awx/main/models/inventory.py:872 msgid "Red Hat Satellite 6" msgstr "" -#: awx/main/models/inventory.py:874 +#: awx/main/models/inventory.py:873 msgid "Red Hat CloudForms" msgstr "" -#: awx/main/models/inventory.py:875 +#: awx/main/models/inventory.py:874 msgid "OpenStack" msgstr "" +#: awx/main/models/inventory.py:875 +msgid "oVirt4" +msgstr "" + #: awx/main/models/inventory.py:876 +msgid "Ansible Tower" +msgstr "" + +#: awx/main/models/inventory.py:877 msgid "Custom Script" msgstr "" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:994 msgid "Inventory source variables in YAML or JSON format." msgstr "" -#: awx/main/models/inventory.py:1012 +#: awx/main/models/inventory.py:1013 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." msgstr "" -#: awx/main/models/inventory.py:1018 +#: awx/main/models/inventory.py:1019 msgid "Limit groups automatically created from inventory source (EC2 only)." msgstr "" -#: awx/main/models/inventory.py:1022 +#: awx/main/models/inventory.py:1023 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:1026 +#: awx/main/models/inventory.py:1027 msgid "Overwrite local variables from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:1031 awx/main/models/jobs.py:159 +#: awx/main/models/inventory.py:1032 awx/main/models/jobs.py:160 #: awx/main/models/projects.py:117 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "" -#: awx/main/models/inventory.py:1064 +#: awx/main/models/inventory.py:1065 msgid "Image ID" msgstr "" -#: awx/main/models/inventory.py:1065 +#: awx/main/models/inventory.py:1066 msgid "Availability Zone" msgstr "" -#: awx/main/models/inventory.py:1066 +#: awx/main/models/inventory.py:1067 msgid "Account" msgstr "" -#: awx/main/models/inventory.py:1067 +#: awx/main/models/inventory.py:1068 msgid "Instance ID" msgstr "" -#: awx/main/models/inventory.py:1068 +#: awx/main/models/inventory.py:1069 msgid "Instance State" msgstr "" -#: awx/main/models/inventory.py:1069 +#: awx/main/models/inventory.py:1070 msgid "Instance Type" msgstr "" -#: awx/main/models/inventory.py:1070 +#: awx/main/models/inventory.py:1071 msgid "Key Name" msgstr "" -#: awx/main/models/inventory.py:1071 +#: awx/main/models/inventory.py:1072 msgid "Region" msgstr "" -#: awx/main/models/inventory.py:1072 +#: awx/main/models/inventory.py:1073 msgid "Security Group" msgstr "" -#: awx/main/models/inventory.py:1073 +#: awx/main/models/inventory.py:1074 msgid "Tags" msgstr "" -#: awx/main/models/inventory.py:1074 +#: awx/main/models/inventory.py:1075 msgid "Tag None" msgstr "" -#: awx/main/models/inventory.py:1075 +#: awx/main/models/inventory.py:1076 msgid "VPC ID" msgstr "" -#: awx/main/models/inventory.py:1138 +#: awx/main/models/inventory.py:1145 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." msgstr "" -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:1152 msgid "Credential is required for a cloud source." msgstr "" -#: awx/main/models/inventory.py:1167 +#: awx/main/models/inventory.py:1155 +msgid "" +"Credentials of type machine, source control, insights and vault are " +"disallowed for custom inventory sources." +msgstr "" + +#: awx/main/models/inventory.py:1179 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "" -#: awx/main/models/inventory.py:1191 +#: awx/main/models/inventory.py:1203 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "" -#: awx/main/models/inventory.py:1212 +#: awx/main/models/inventory.py:1224 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "" -#: awx/main/models/inventory.py:1247 +#: awx/main/models/inventory.py:1259 msgid "Project containing inventory file used as source." msgstr "" -#: awx/main/models/inventory.py:1395 +#: awx/main/models/inventory.py:1407 #, python-format msgid "" "Unable to configure this item for cloud sync. It is already managed by %s." msgstr "" -#: awx/main/models/inventory.py:1405 +#: awx/main/models/inventory.py:1417 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." msgstr "" -#: awx/main/models/inventory.py:1412 +#: awx/main/models/inventory.py:1424 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." msgstr "" -#: awx/main/models/inventory.py:1418 +#: awx/main/models/inventory.py:1430 msgid "SCM type sources must set `overwrite_vars` to `true`." msgstr "" -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1435 msgid "Cannot set source_path if not SCM type." msgstr "" -#: awx/main/models/inventory.py:1448 +#: awx/main/models/inventory.py:1460 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "" -#: awx/main/models/inventory.py:1561 +#: awx/main/models/inventory.py:1573 msgid "Inventory script contents" msgstr "" -#: awx/main/models/inventory.py:1566 +#: awx/main/models/inventory.py:1578 msgid "Organization owning this inventory script" msgstr "" -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:66 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" msgstr "" -#: awx/main/models/jobs.py:163 +#: awx/main/models/jobs.py:164 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " "at the end of a playbook run to the database and caching facts for use by " "Ansible." msgstr "" -#: awx/main/models/jobs.py:172 +#: awx/main/models/jobs.py:173 msgid "You must provide an SSH credential." msgstr "" -#: awx/main/models/jobs.py:180 +#: awx/main/models/jobs.py:181 msgid "You must provide a Vault credential." msgstr "" -#: awx/main/models/jobs.py:316 +#: awx/main/models/jobs.py:317 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "" -#: awx/main/models/jobs.py:320 +#: awx/main/models/jobs.py:321 msgid "Job Template must provide 'credential' or allow prompting for it." msgstr "" -#: awx/main/models/jobs.py:422 +#: awx/main/models/jobs.py:427 msgid "Cannot override job_type to or from a scan job." msgstr "" -#: awx/main/models/jobs.py:488 awx/main/models/projects.py:263 +#: awx/main/models/jobs.py:493 awx/main/models/projects.py:263 msgid "SCM Revision" msgstr "" -#: awx/main/models/jobs.py:489 +#: awx/main/models/jobs.py:494 msgid "The SCM Revision from the Project used for this job, if available" msgstr "" -#: awx/main/models/jobs.py:497 +#: awx/main/models/jobs.py:502 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" msgstr "" -#: awx/main/models/jobs.py:802 +#: awx/main/models/jobs.py:809 msgid "job host summaries" msgstr "" -#: awx/main/models/jobs.py:906 +#: awx/main/models/jobs.py:913 msgid "Host Failure" msgstr "" -#: awx/main/models/jobs.py:909 awx/main/models/jobs.py:923 +#: awx/main/models/jobs.py:916 awx/main/models/jobs.py:930 msgid "No Hosts Remaining" msgstr "" -#: awx/main/models/jobs.py:910 +#: awx/main/models/jobs.py:917 msgid "Host Polling" msgstr "" -#: awx/main/models/jobs.py:911 +#: awx/main/models/jobs.py:918 msgid "Host Async OK" msgstr "" -#: awx/main/models/jobs.py:912 +#: awx/main/models/jobs.py:919 msgid "Host Async Failure" msgstr "" -#: awx/main/models/jobs.py:913 +#: awx/main/models/jobs.py:920 msgid "Item OK" msgstr "" -#: awx/main/models/jobs.py:914 +#: awx/main/models/jobs.py:921 msgid "Item Failed" msgstr "" -#: awx/main/models/jobs.py:915 +#: awx/main/models/jobs.py:922 msgid "Item Skipped" msgstr "" -#: awx/main/models/jobs.py:916 +#: awx/main/models/jobs.py:923 msgid "Host Retry" msgstr "" -#: awx/main/models/jobs.py:918 +#: awx/main/models/jobs.py:925 msgid "File Difference" msgstr "" -#: awx/main/models/jobs.py:919 +#: awx/main/models/jobs.py:926 msgid "Playbook Started" msgstr "" -#: awx/main/models/jobs.py:920 +#: awx/main/models/jobs.py:927 msgid "Running Handlers" msgstr "" -#: awx/main/models/jobs.py:921 +#: awx/main/models/jobs.py:928 msgid "Including File" msgstr "" -#: awx/main/models/jobs.py:922 +#: awx/main/models/jobs.py:929 msgid "No Hosts Matched" msgstr "" -#: awx/main/models/jobs.py:924 +#: awx/main/models/jobs.py:931 msgid "Task Started" msgstr "" -#: awx/main/models/jobs.py:926 +#: awx/main/models/jobs.py:933 msgid "Variables Prompted" msgstr "" -#: awx/main/models/jobs.py:927 +#: awx/main/models/jobs.py:934 msgid "Gathering Facts" msgstr "" -#: awx/main/models/jobs.py:928 +#: awx/main/models/jobs.py:935 msgid "internal: on Import for Host" msgstr "" -#: awx/main/models/jobs.py:929 +#: awx/main/models/jobs.py:936 msgid "internal: on Not Import for Host" msgstr "" -#: awx/main/models/jobs.py:930 +#: awx/main/models/jobs.py:937 msgid "Play Started" msgstr "" -#: awx/main/models/jobs.py:931 +#: awx/main/models/jobs.py:938 msgid "Playbook Complete" msgstr "" -#: awx/main/models/jobs.py:1363 +#: awx/main/models/jobs.py:1351 msgid "Remove jobs older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1364 +#: awx/main/models/jobs.py:1352 msgid "Remove activity stream entries older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1365 +#: awx/main/models/jobs.py:1353 msgid "Purge and/or reduce the granularity of system tracking data" msgstr "" @@ -3038,8 +3087,7 @@ msgstr "" msgid "The Rampart/Instance group the job was run under" msgstr "" -#: awx/main/notifications/base.py:17 -#: awx/main/notifications/email_backend.py:28 +#: awx/main/notifications/base.py:17 awx/main/notifications/email_backend.py:28 msgid "" "{} #{} had status {}, view details at {}\n" "\n" @@ -3076,13 +3124,13 @@ msgstr "" msgid "Error sending notification webhook: {}" msgstr "" -#: awx/main/scheduler/__init__.py:184 +#: awx/main/scheduler/task_manager.py:197 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" msgstr "" -#: awx/main/scheduler/__init__.py:188 +#: awx/main/scheduler/task_manager.py:201 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" @@ -3100,7 +3148,7 @@ msgstr "" msgid "status_str must be either succeeded or failed" msgstr "" -#: awx/main/tasks.py:1531 +#: awx/main/tasks.py:1549 msgid "Dependent inventory update {} was canceled." msgstr "" @@ -3109,38 +3157,38 @@ msgstr "" msgid "Unable to convert \"%s\" to boolean" msgstr "" -#: awx/main/utils/common.py:209 +#: awx/main/utils/common.py:235 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "" -#: awx/main/utils/common.py:216 awx/main/utils/common.py:228 -#: awx/main/utils/common.py:247 +#: awx/main/utils/common.py:242 awx/main/utils/common.py:254 +#: awx/main/utils/common.py:273 #, python-format msgid "Invalid %s URL" msgstr "" -#: awx/main/utils/common.py:218 awx/main/utils/common.py:257 +#: awx/main/utils/common.py:244 awx/main/utils/common.py:283 #, python-format msgid "Unsupported %s URL" msgstr "" -#: awx/main/utils/common.py:259 +#: awx/main/utils/common.py:285 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "" -#: awx/main/utils/common.py:261 +#: awx/main/utils/common.py:287 #, python-format msgid "Host is required for %s URL" msgstr "" -#: awx/main/utils/common.py:279 +#: awx/main/utils/common.py:305 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "" -#: awx/main/utils/common.py:285 +#: awx/main/utils/common.py:311 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "" @@ -3251,287 +3299,287 @@ msgstr "" msgid "A server error has occurred." msgstr "" -#: awx/settings/defaults.py:664 +#: awx/settings/defaults.py:665 msgid "US East (Northern Virginia)" msgstr "" -#: awx/settings/defaults.py:665 +#: awx/settings/defaults.py:666 msgid "US East (Ohio)" msgstr "" -#: awx/settings/defaults.py:666 +#: awx/settings/defaults.py:667 msgid "US West (Oregon)" msgstr "" -#: awx/settings/defaults.py:667 +#: awx/settings/defaults.py:668 msgid "US West (Northern California)" msgstr "" -#: awx/settings/defaults.py:668 +#: awx/settings/defaults.py:669 msgid "Canada (Central)" msgstr "" -#: awx/settings/defaults.py:669 +#: awx/settings/defaults.py:670 msgid "EU (Frankfurt)" msgstr "" -#: awx/settings/defaults.py:670 +#: awx/settings/defaults.py:671 msgid "EU (Ireland)" msgstr "" -#: awx/settings/defaults.py:671 +#: awx/settings/defaults.py:672 msgid "EU (London)" msgstr "" -#: awx/settings/defaults.py:672 +#: awx/settings/defaults.py:673 msgid "Asia Pacific (Singapore)" msgstr "" -#: awx/settings/defaults.py:673 +#: awx/settings/defaults.py:674 msgid "Asia Pacific (Sydney)" msgstr "" -#: awx/settings/defaults.py:674 +#: awx/settings/defaults.py:675 msgid "Asia Pacific (Tokyo)" msgstr "" -#: awx/settings/defaults.py:675 +#: awx/settings/defaults.py:676 msgid "Asia Pacific (Seoul)" msgstr "" -#: awx/settings/defaults.py:676 +#: awx/settings/defaults.py:677 msgid "Asia Pacific (Mumbai)" msgstr "" -#: awx/settings/defaults.py:677 +#: awx/settings/defaults.py:678 msgid "South America (Sao Paulo)" msgstr "" -#: awx/settings/defaults.py:678 +#: awx/settings/defaults.py:679 msgid "US West (GovCloud)" msgstr "" -#: awx/settings/defaults.py:679 +#: awx/settings/defaults.py:680 msgid "China (Beijing)" msgstr "" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:729 msgid "US East 1 (B)" msgstr "" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:730 msgid "US East 1 (C)" msgstr "" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:731 msgid "US East 1 (D)" msgstr "" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:732 msgid "US East 4 (A)" msgstr "" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:733 msgid "US East 4 (B)" msgstr "" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:734 msgid "US East 4 (C)" msgstr "" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:735 msgid "US Central (A)" msgstr "" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:736 msgid "US Central (B)" msgstr "" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:737 msgid "US Central (C)" msgstr "" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:738 msgid "US Central (F)" msgstr "" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:739 msgid "US West (A)" msgstr "" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:740 msgid "US West (B)" msgstr "" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:741 msgid "US West (C)" msgstr "" -#: awx/settings/defaults.py:741 +#: awx/settings/defaults.py:742 msgid "Europe West 1 (B)" msgstr "" -#: awx/settings/defaults.py:742 +#: awx/settings/defaults.py:743 msgid "Europe West 1 (C)" msgstr "" -#: awx/settings/defaults.py:743 +#: awx/settings/defaults.py:744 msgid "Europe West 1 (D)" msgstr "" -#: awx/settings/defaults.py:744 +#: awx/settings/defaults.py:745 msgid "Europe West 2 (A)" msgstr "" -#: awx/settings/defaults.py:745 +#: awx/settings/defaults.py:746 msgid "Europe West 2 (B)" msgstr "" -#: awx/settings/defaults.py:746 +#: awx/settings/defaults.py:747 msgid "Europe West 2 (C)" msgstr "" -#: awx/settings/defaults.py:747 +#: awx/settings/defaults.py:748 msgid "Asia East (A)" msgstr "" -#: awx/settings/defaults.py:748 +#: awx/settings/defaults.py:749 msgid "Asia East (B)" msgstr "" -#: awx/settings/defaults.py:749 +#: awx/settings/defaults.py:750 msgid "Asia East (C)" msgstr "" -#: awx/settings/defaults.py:750 +#: awx/settings/defaults.py:751 msgid "Asia Southeast (A)" msgstr "" -#: awx/settings/defaults.py:751 +#: awx/settings/defaults.py:752 msgid "Asia Southeast (B)" msgstr "" -#: awx/settings/defaults.py:752 +#: awx/settings/defaults.py:753 msgid "Asia Northeast (A)" msgstr "" -#: awx/settings/defaults.py:753 +#: awx/settings/defaults.py:754 msgid "Asia Northeast (B)" msgstr "" -#: awx/settings/defaults.py:754 +#: awx/settings/defaults.py:755 msgid "Asia Northeast (C)" msgstr "" -#: awx/settings/defaults.py:755 +#: awx/settings/defaults.py:756 msgid "Australia Southeast (A)" msgstr "" -#: awx/settings/defaults.py:756 +#: awx/settings/defaults.py:757 msgid "Australia Southeast (B)" msgstr "" -#: awx/settings/defaults.py:757 +#: awx/settings/defaults.py:758 msgid "Australia Southeast (C)" msgstr "" -#: awx/settings/defaults.py:781 +#: awx/settings/defaults.py:780 msgid "US East" msgstr "" -#: awx/settings/defaults.py:782 +#: awx/settings/defaults.py:781 msgid "US East 2" msgstr "" -#: awx/settings/defaults.py:783 +#: awx/settings/defaults.py:782 msgid "US Central" msgstr "" -#: awx/settings/defaults.py:784 +#: awx/settings/defaults.py:783 msgid "US North Central" msgstr "" -#: awx/settings/defaults.py:785 +#: awx/settings/defaults.py:784 msgid "US South Central" msgstr "" -#: awx/settings/defaults.py:786 +#: awx/settings/defaults.py:785 msgid "US West Central" msgstr "" -#: awx/settings/defaults.py:787 +#: awx/settings/defaults.py:786 msgid "US West" msgstr "" -#: awx/settings/defaults.py:788 +#: awx/settings/defaults.py:787 msgid "US West 2" msgstr "" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:788 msgid "Canada East" msgstr "" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:789 msgid "Canada Central" msgstr "" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:790 msgid "Brazil South" msgstr "" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:791 msgid "Europe North" msgstr "" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:792 msgid "Europe West" msgstr "" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:793 msgid "UK West" msgstr "" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:794 msgid "UK South" msgstr "" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:795 msgid "Asia East" msgstr "" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:796 msgid "Asia Southeast" msgstr "" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:797 msgid "Australia East" msgstr "" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:798 msgid "Australia Southeast" msgstr "" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:799 msgid "India West" msgstr "" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:800 msgid "India South" msgstr "" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:801 msgid "Japan East" msgstr "" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:802 msgid "Japan West" msgstr "" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:803 msgid "Korea Central" msgstr "" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:804 msgid "Korea South" msgstr "" @@ -4167,6 +4215,7 @@ msgid "SAML Team Map" msgstr "" #: awx/sso/fields.py:123 +#, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "" @@ -4183,78 +4232,96 @@ msgid "Subtree" msgstr "" #: awx/sso/fields.py:214 +#, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "" #: awx/sso/fields.py:215 +#, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" #: awx/sso/fields.py:251 +#, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." msgstr "" #: awx/sso/fields.py:289 +#, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "" #: awx/sso/fields.py:306 +#, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" #: awx/sso/fields.py:334 +#, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "" #: awx/sso/fields.py:350 awx/sso/fields.py:517 +#, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type} " "instead." msgstr "" #: awx/sso/fields.py:386 +#, python-brace-format msgid "Missing key(s): {missing_keys}." msgstr "" #: awx/sso/fields.py:387 +#, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "" #: awx/sso/fields.py:436 awx/sso/fields.py:553 +#, python-brace-format msgid "Invalid key(s) for organization map: {invalid_keys}." msgstr "" #: awx/sso/fields.py:454 +#, python-brace-format msgid "Missing required key for team map: {invalid_keys}." msgstr "" #: awx/sso/fields.py:455 awx/sso/fields.py:572 +#, python-brace-format msgid "Invalid key(s) for team map: {invalid_keys}." msgstr "" #: awx/sso/fields.py:571 +#, python-brace-format msgid "Missing required key for team map: {missing_keys}." msgstr "" #: awx/sso/fields.py:589 +#, python-brace-format msgid "Missing required key(s) for org info record: {missing_keys}." msgstr "" #: awx/sso/fields.py:602 +#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "" #: awx/sso/fields.py:621 +#, python-brace-format msgid "Missing required key(s) for contact: {missing_keys}." msgstr "" #: awx/sso/fields.py:633 +#, python-brace-format msgid "Missing required key(s) for IdP: {missing_keys}." msgstr "" #: awx/sso/pipeline.py:24 +#, python-brace-format msgid "An account cannot be found for {0}" msgstr "" @@ -4346,6 +4413,7 @@ msgid "Make a PATCH request on the %(name)s resource" msgstr "" #: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 +#: awx/ui/conf.py:63 msgid "UI" msgstr "" @@ -4392,6 +4460,15 @@ msgid "" "and JPEG formats are supported." msgstr "" +#: awx/ui/conf.py:60 +msgid "Max Job Events Retreived by UI" +msgstr "" + +#: awx/ui/conf.py:61 +msgid "" +"Maximum number of job events for the UI to retreive within a single request." +msgstr "" + #: awx/ui/fields.py:29 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " diff --git a/awx/locale/es/LC_MESSAGES/django.po b/awx/locale/es/LC_MESSAGES/django.po index 6e26752ef1..d24f549215 100644 --- a/awx/locale/es/LC_MESSAGES/django.po +++ b/awx/locale/es/LC_MESSAGES/django.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-08-27 19:27+0000\n" +"POT-Creation-Date: 2017-11-30 20:23+0000\n" "PO-Revision-Date: 2017-09-01 06:52+0000\n" "Last-Translator: edrh01 \n" "Language-Team: \n" @@ -21,7 +21,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Zanata 4.2.1\n" +"X-Generator: Zanata 4.3.2\n" #: awx/api/authentication.py:67 msgid "Invalid token header. No credentials provided." @@ -89,25 +89,30 @@ msgstr "Filtrar sobre %s no está permitido." msgid "Loops not allowed in filters, detected on field {}." msgstr "Bucles no permitidos en los filtros, detectados en el campo {}." -#: awx/api/filters.py:297 +#: awx/api/filters.py:171 +#, python-brace-format +msgid "Invalid {field_name} id: {field_id}" +msgstr "" + +#: awx/api/filters.py:302 #, python-format msgid "cannot filter on kind %s" msgstr "no se puede filtrar en el tipo %s" -#: awx/api/filters.py:404 +#: awx/api/filters.py:409 #, python-format msgid "cannot order by field %s" msgstr "no se puede ordenar por el campo %s" -#: awx/api/generics.py:515 awx/api/generics.py:577 +#: awx/api/generics.py:550 awx/api/generics.py:612 msgid "\"id\" field must be an integer." msgstr "El campo \"id\" debe ser un número entero." -#: awx/api/generics.py:574 +#: awx/api/generics.py:609 msgid "\"id\" is required to disassociate" msgstr "\"id\" es necesario para desasociar" -#: awx/api/generics.py:625 +#: awx/api/generics.py:660 msgid "{} 'id' field is missing." msgstr "Falta el campo {} 'id'." @@ -189,7 +194,7 @@ msgstr "Tarea en flujo de trabajo" msgid "Workflow Template" msgstr "Plantilla de flujo de trabajo" -#: awx/api/serializers.py:697 awx/api/serializers.py:755 awx/api/views.py:4314 +#: awx/api/serializers.py:701 awx/api/serializers.py:759 awx/api/views.py:4365 #, python-format msgid "" "Standard Output too large to display (%(text_size)d bytes), only download " @@ -199,38 +204,38 @@ msgstr "" "sólo está soportada la descarga para tamaños por encima de " "%(supported_size)d bytes" -#: awx/api/serializers.py:770 +#: awx/api/serializers.py:774 msgid "Write-only field used to change the password." msgstr "Campo de sólo escritura utilizado para cambiar la contraseña." -#: awx/api/serializers.py:772 +#: awx/api/serializers.py:776 msgid "Set if the account is managed by an external service" msgstr "Establecer si la cuenta es administrada por un servicio externo" -#: awx/api/serializers.py:796 +#: awx/api/serializers.py:800 msgid "Password required for new User." msgstr "Contraseña requerida para un usuario nuevo." -#: awx/api/serializers.py:882 +#: awx/api/serializers.py:886 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "Incapaz de cambiar %s en usuario gestionado por LDAP." -#: awx/api/serializers.py:1046 +#: awx/api/serializers.py:1050 msgid "Organization is missing" msgstr "Organización no encontrada." -#: awx/api/serializers.py:1050 +#: awx/api/serializers.py:1054 msgid "Update options must be set to false for manual projects." msgstr "" "Opciones de actualización deben ser establecidas a false para proyectos " "manuales." -#: awx/api/serializers.py:1056 +#: awx/api/serializers.py:1060 msgid "Array of playbooks available within this project." msgstr "Colección de playbooks disponibles dentro de este proyecto." -#: awx/api/serializers.py:1075 +#: awx/api/serializers.py:1079 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." @@ -238,67 +243,71 @@ msgstr "" "Colección de archivos de inventario y directorios disponibles dentro de este" " proyecto, no global." -#: awx/api/serializers.py:1282 +#: awx/api/serializers.py:1201 +msgid "Smart inventories must specify host_filter" +msgstr "" + +#: awx/api/serializers.py:1303 #, python-format msgid "Invalid port specification: %s" msgstr "Especificación de puerto inválido: %s" -#: awx/api/serializers.py:1293 +#: awx/api/serializers.py:1314 msgid "Cannot create Host for Smart Inventory" msgstr "No es posible crear un host para el Inventario inteligente" -#: awx/api/serializers.py:1315 awx/api/serializers.py:3218 -#: awx/api/serializers.py:3303 awx/main/validators.py:198 +#: awx/api/serializers.py:1336 awx/api/serializers.py:3321 +#: awx/api/serializers.py:3406 awx/main/validators.py:198 msgid "Must be valid JSON or YAML." msgstr "Debe ser un válido JSON o YAML." -#: awx/api/serializers.py:1411 +#: awx/api/serializers.py:1432 msgid "Invalid group name." msgstr "Nombre de grupo inválido." -#: awx/api/serializers.py:1416 +#: awx/api/serializers.py:1437 msgid "Cannot create Group for Smart Inventory" msgstr "No es posible crear un grupo para el Inventario inteligente" -#: awx/api/serializers.py:1488 +#: awx/api/serializers.py:1509 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" "El script debe empezar con una secuencia hashbang, p.e.... #!/usr/bin/env " "python" -#: awx/api/serializers.py:1534 +#: awx/api/serializers.py:1555 msgid "`{}` is a prohibited environment variable" msgstr "`{}` es una variable de entorno prohibida" -#: awx/api/serializers.py:1545 +#: awx/api/serializers.py:1566 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "Si 'source' es 'custom', 'source_script' debe ser especificado." -#: awx/api/serializers.py:1551 +#: awx/api/serializers.py:1572 msgid "Must provide an inventory." msgstr "Debe proporcionar un inventario." -#: awx/api/serializers.py:1555 +#: awx/api/serializers.py:1576 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" "El 'source_script' no pertenece a la misma organización que el inventario." -#: awx/api/serializers.py:1557 +#: awx/api/serializers.py:1578 msgid "'source_script' doesn't exist." msgstr "'source_script' no existe." -#: awx/api/serializers.py:1581 +#: awx/api/serializers.py:1602 msgid "Automatic group relationship, will be removed in 3.3" msgstr "Relación de grupo automática; se eliminará en 3.3" -#: awx/api/serializers.py:1658 +#: awx/api/serializers.py:1679 msgid "Cannot use manual project for SCM-based inventory." msgstr "No se puede usar el proyecto manual para el inventario basado en SCM." -#: awx/api/serializers.py:1664 +#: awx/api/serializers.py:1685 msgid "" "Manual inventory sources are created automatically when a group is created " "in the v1 API." @@ -306,53 +315,59 @@ msgstr "" "Las fuentes de inventario manuales se crean automáticamente cuando se crea " "un grupo en la API v1." -#: awx/api/serializers.py:1669 +#: awx/api/serializers.py:1690 msgid "Setting not compatible with existing schedules." msgstr "Configuración no compatible con programaciones existentes." -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1695 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "" "No es posible crear una fuente de inventarios para el Inventario inteligente" -#: awx/api/serializers.py:1688 +#: awx/api/serializers.py:1709 #, python-format msgid "Cannot set %s if not SCM type." msgstr "No es posible definir %s si no es de tipo SCM." -#: awx/api/serializers.py:1929 +#: awx/api/serializers.py:1950 msgid "Modifications not allowed for managed credential types" msgstr "" "Modificaciones no permitidas para los tipos de credenciales administradas" -#: awx/api/serializers.py:1934 +#: awx/api/serializers.py:1955 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "" "No se permiten las modificaciones a entradas para los tipos de credenciales " "que están en uso" -#: awx/api/serializers.py:1940 +#: awx/api/serializers.py:1961 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "Debe ser 'cloud' o 'net', no %s" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1967 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "" "'ask_at_runtime' no es compatible con las credenciales personalizadas." -#: awx/api/serializers.py:2119 +#: awx/api/serializers.py:2140 #, python-format msgid "\"%s\" is not a valid choice" msgstr "\"%s\" no es una opción válida" -#: awx/api/serializers.py:2138 +#: awx/api/serializers.py:2159 #, python-format msgid "'%s' is not a valid field for %s" msgstr "'%s' no es un campo válido para %s" -#: awx/api/serializers.py:2150 +#: awx/api/serializers.py:2180 +msgid "" +"You cannot change the credential type of the credential, as it may break the" +" functionality of the resources using it." +msgstr "" + +#: awx/api/serializers.py:2191 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." @@ -360,7 +375,7 @@ msgstr "" "Campo de sólo escritura utilizado para añadir usuario a rol de propietario. " "Si se indica, no otorgar equipo u organización. Sólo válido para creación." -#: awx/api/serializers.py:2155 +#: awx/api/serializers.py:2196 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." @@ -368,7 +383,7 @@ msgstr "" "Campo de sólo escritura para añadir equipo a un rol propietario.Si se " "indica, no otorgar usuario u organización. Sólo válido para creación." -#: awx/api/serializers.py:2160 +#: awx/api/serializers.py:2201 msgid "" "Inherit permissions from organization roles. If provided on creation, do not" " give either user or team." @@ -376,125 +391,129 @@ msgstr "" "Permisos heredados desde roles de organización. Si se indica, no otorgar " "usuario o equipo." -#: awx/api/serializers.py:2176 +#: awx/api/serializers.py:2217 msgid "Missing 'user', 'team', or 'organization'." msgstr "No encontrado 'user', 'team' u 'organization'" -#: awx/api/serializers.py:2216 +#: awx/api/serializers.py:2257 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "" "Credenciales de organización deben ser establecidas y coincidir antes de ser" " asignadas a un equipo" -#: awx/api/serializers.py:2378 +#: awx/api/serializers.py:2424 msgid "You must provide a cloud credential." msgstr "Debe proporcionar una credencial de nube." -#: awx/api/serializers.py:2379 +#: awx/api/serializers.py:2425 msgid "You must provide a network credential." msgstr "Debe indicar un credencial de red." -#: awx/api/serializers.py:2395 +#: awx/api/serializers.py:2441 msgid "This field is required." msgstr "Este campo es obligatorio." -#: awx/api/serializers.py:2397 awx/api/serializers.py:2399 +#: awx/api/serializers.py:2443 awx/api/serializers.py:2445 msgid "Playbook not found for project." msgstr "Playbook no encontrado para el proyecto." -#: awx/api/serializers.py:2401 +#: awx/api/serializers.py:2447 msgid "Must select playbook for project." msgstr "Debe seleccionar un playbook para el proyecto." -#: awx/api/serializers.py:2476 +#: awx/api/serializers.py:2522 msgid "Must either set a default value or ask to prompt on launch." msgstr "" "Debe establecer un valor por defecto o preguntar por valor al ejecutar." -#: awx/api/serializers.py:2478 awx/main/models/jobs.py:325 +#: awx/api/serializers.py:2524 awx/main/models/jobs.py:326 msgid "Job types 'run' and 'check' must have assigned a project." msgstr "Tipos de trabajo 'run' y 'check' deben tener asignado un proyecto." -#: awx/api/serializers.py:2549 +#: awx/api/serializers.py:2611 msgid "Invalid job template." msgstr "Plantilla de trabajo inválida" -#: awx/api/serializers.py:2630 -msgid "Credential not found or deleted." -msgstr "Credencial no encontrado o eliminado." +#: awx/api/serializers.py:2708 +msgid "Neither credential nor vault credential provided." +msgstr "" -#: awx/api/serializers.py:2632 +#: awx/api/serializers.py:2711 msgid "Job Template Project is missing or undefined." msgstr "Proyecto en la plantilla de trabajo no encontrado o no definido." -#: awx/api/serializers.py:2634 +#: awx/api/serializers.py:2713 msgid "Job Template Inventory is missing or undefined." msgstr "Inventario en la plantilla de trabajo no encontrado o no definido." -#: awx/api/serializers.py:2921 +#: awx/api/serializers.py:2782 awx/main/tasks.py:2186 +msgid "{} are prohibited from use in ad hoc commands." +msgstr "" + +#: awx/api/serializers.py:3008 #, python-format msgid "%(job_type)s is not a valid job type. The choices are %(choices)s." msgstr "" "j%(job_type)s no es un tipo de trabajo válido. Las opciones son %(choices)s." -#: awx/api/serializers.py:2926 +#: awx/api/serializers.py:3013 msgid "Workflow job template is missing during creation." msgstr "" "Plantilla de tarea en el flujo de trabajo no encontrada durante la creación." -#: awx/api/serializers.py:2931 +#: awx/api/serializers.py:3018 #, python-format msgid "Cannot nest a %s inside a WorkflowJobTemplate" msgstr "No es posible anidar un %s dentro de un WorkflowJobTemplate" -#: awx/api/serializers.py:3188 +#: awx/api/serializers.py:3291 #, python-format msgid "Job Template '%s' is missing or undefined." msgstr "Plantilla de trabajo '%s' no encontrada o no definida." -#: awx/api/serializers.py:3191 +#: awx/api/serializers.py:3294 msgid "The inventory associated with this Job Template is being deleted." msgstr "" "Se está eliminando el inventario asociado con esta plantilla de trabajo." -#: awx/api/serializers.py:3232 awx/api/views.py:2991 +#: awx/api/serializers.py:3335 awx/api/views.py:3023 #, python-format msgid "Cannot assign multiple %s credentials." msgstr "No se pueden asignar múltiples credenciales %s." -#: awx/api/serializers.py:3234 awx/api/views.py:2994 +#: awx/api/serializers.py:3337 awx/api/views.py:3026 msgid "Extra credentials must be network or cloud." msgstr "Las credenciales adicionales deben ser red o nube." -#: awx/api/serializers.py:3371 +#: awx/api/serializers.py:3474 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" "Campos obligatorios no definidos para la configuración de notificación: " "notification_type" -#: awx/api/serializers.py:3394 +#: awx/api/serializers.py:3497 msgid "No values specified for field '{}'" msgstr "Ningún valor especificado para el campo '{}'" -#: awx/api/serializers.py:3399 +#: awx/api/serializers.py:3502 msgid "Missing required fields for Notification Configuration: {}." msgstr "Campos no definidos para la configuración de notificación: {}." -#: awx/api/serializers.py:3402 +#: awx/api/serializers.py:3505 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "Tipo incorrecto en la configuración del campo '{} ', esperado {}." -#: awx/api/serializers.py:3455 +#: awx/api/serializers.py:3558 msgid "Inventory Source must be a cloud resource." msgstr "Fuente del inventario debe ser un recurso cloud." -#: awx/api/serializers.py:3457 +#: awx/api/serializers.py:3560 msgid "Manual Project cannot have a schedule set." msgstr "El proyecto manual no puede tener una programación establecida." -#: awx/api/serializers.py:3460 +#: awx/api/serializers.py:3563 msgid "" "Inventory sources with `update_on_project_update` cannot be scheduled. " "Schedule its source project `{}` instead." @@ -502,72 +521,72 @@ msgstr "" "No se pueden programar las fuentes de inventario con " "`update_on_project_update. En su lugar, programe su proyecto fuente `{}`." -#: awx/api/serializers.py:3479 +#: awx/api/serializers.py:3582 msgid "Projects and inventory updates cannot accept extra variables." msgstr "" "Las actualizaciones de inventarios y proyectos no pueden aceptar variables " "adicionales." -#: awx/api/serializers.py:3501 +#: awx/api/serializers.py:3604 msgid "" "DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ" msgstr "" "DTSTART necesario en 'rrule'. El valor debe coincidir: " "DTSTART:YYYYMMDDTHHMMSSZ" -#: awx/api/serializers.py:3503 +#: awx/api/serializers.py:3606 msgid "Multiple DTSTART is not supported." msgstr "Múltiple DTSTART no está soportado." -#: awx/api/serializers.py:3505 +#: awx/api/serializers.py:3608 msgid "RRULE require in rrule." msgstr "RRULE requerido en rrule" -#: awx/api/serializers.py:3507 +#: awx/api/serializers.py:3610 msgid "Multiple RRULE is not supported." msgstr "Múltiple RRULE no está soportado." -#: awx/api/serializers.py:3509 +#: awx/api/serializers.py:3612 msgid "INTERVAL required in rrule." msgstr "INTERVAL requerido en 'rrule'." -#: awx/api/serializers.py:3511 +#: awx/api/serializers.py:3614 msgid "TZID is not supported." msgstr "TZID no está soportado." -#: awx/api/serializers.py:3513 +#: awx/api/serializers.py:3616 msgid "SECONDLY is not supported." msgstr "SECONDLY no está soportado." -#: awx/api/serializers.py:3515 +#: awx/api/serializers.py:3618 msgid "Multiple BYMONTHDAYs not supported." msgstr "Multiple BYMONTHDAYs no soportado." -#: awx/api/serializers.py:3517 +#: awx/api/serializers.py:3620 msgid "Multiple BYMONTHs not supported." msgstr "Multiple BYMONTHs no soportado." -#: awx/api/serializers.py:3519 +#: awx/api/serializers.py:3622 msgid "BYDAY with numeric prefix not supported." msgstr "BYDAY con prefijo numérico no soportado." -#: awx/api/serializers.py:3521 +#: awx/api/serializers.py:3624 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY no soportado." -#: awx/api/serializers.py:3523 +#: awx/api/serializers.py:3626 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO no soportado." -#: awx/api/serializers.py:3527 +#: awx/api/serializers.py:3630 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 no está soportada." -#: awx/api/serializers.py:3531 +#: awx/api/serializers.py:3634 msgid "rrule parsing failed validation." msgstr "Validación fallida analizando 'rrule'" -#: awx/api/serializers.py:3631 +#: awx/api/serializers.py:3760 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" @@ -575,7 +594,7 @@ msgstr "" "Un resumen de los valores nuevos y cambiados cuando un objeto es creado, " "actualizado o eliminado." -#: awx/api/serializers.py:3633 +#: awx/api/serializers.py:3762 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " @@ -585,7 +604,7 @@ msgstr "" "afectado. Para asociar o desasociar eventos éste es el tipo de objeto " "asociado o desasociado con object2." -#: awx/api/serializers.py:3636 +#: awx/api/serializers.py:3765 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated" @@ -594,163 +613,163 @@ msgstr "" "Vacío para crear, actualizar y eliminar eventos. Para asociar y desasociar " "eventos éste es el tipo de objetos que object1 con el está asociado." -#: awx/api/serializers.py:3639 +#: awx/api/serializers.py:3768 msgid "The action taken with respect to the given object(s)." msgstr "La acción tomada al respeto al/los especificado(s) objeto(s)." -#: awx/api/serializers.py:3749 +#: awx/api/serializers.py:3885 msgid "Unable to login with provided credentials." msgstr "Incapaz de iniciar sesión con los credenciales indicados." -#: awx/api/serializers.py:3751 +#: awx/api/serializers.py:3887 msgid "Must include \"username\" and \"password\"." msgstr "Debe incluir \"usuario\" y \"contraseña\"." -#: awx/api/views.py:106 +#: awx/api/views.py:108 msgid "Your license does not allow use of the activity stream." msgstr "Su licencia no permite el uso de flujo de actividad." -#: awx/api/views.py:116 +#: awx/api/views.py:118 msgid "Your license does not permit use of system tracking." msgstr "Su licencia no permite el uso de sistema de rastreo." -#: awx/api/views.py:126 +#: awx/api/views.py:128 msgid "Your license does not allow use of workflows." msgstr "Su licencia no permite el uso de flujos de trabajo." -#: awx/api/views.py:140 +#: awx/api/views.py:142 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" "No es posible eliminar un recurso de trabajo cuando la tarea del flujo de " "trabajo está en ejecución." -#: awx/api/views.py:144 +#: awx/api/views.py:146 msgid "Cannot delete running job resource." msgstr "No es posible eliminar el recurso de trabajo en ejecución." -#: awx/api/views.py:153 awx/templates/rest_framework/api.html:28 +#: awx/api/views.py:155 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "REST API" -#: awx/api/views.py:162 awx/templates/rest_framework/api.html:4 +#: awx/api/views.py:164 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "API REST de AWX" -#: awx/api/views.py:224 +#: awx/api/views.py:226 msgid "Version 1" msgstr "Version 1" -#: awx/api/views.py:228 +#: awx/api/views.py:230 msgid "Version 2" msgstr "Versión 2" -#: awx/api/views.py:239 +#: awx/api/views.py:241 msgid "Ping" msgstr "Ping" -#: awx/api/views.py:270 awx/conf/apps.py:12 +#: awx/api/views.py:272 awx/conf/apps.py:12 msgid "Configuration" msgstr "Configuración" -#: awx/api/views.py:323 +#: awx/api/views.py:325 msgid "Invalid license data" msgstr "Datos de licencia inválidos." -#: awx/api/views.py:325 +#: awx/api/views.py:327 msgid "Missing 'eula_accepted' property" msgstr "Propiedad 'eula_accepted' no encontrada" -#: awx/api/views.py:329 +#: awx/api/views.py:331 msgid "'eula_accepted' value is invalid" msgstr "Valor 'eula_accepted' no es válido" -#: awx/api/views.py:332 +#: awx/api/views.py:334 msgid "'eula_accepted' must be True" msgstr "'eula_accepted' debe ser True" -#: awx/api/views.py:339 +#: awx/api/views.py:341 msgid "Invalid JSON" msgstr "JSON inválido" -#: awx/api/views.py:347 +#: awx/api/views.py:349 msgid "Invalid License" msgstr "Licencia inválida" -#: awx/api/views.py:357 +#: awx/api/views.py:359 msgid "Invalid license" msgstr "Licencia inválida" -#: awx/api/views.py:365 +#: awx/api/views.py:367 #, python-format msgid "Failed to remove license (%s)" msgstr "Error al eliminar licencia (%s)" -#: awx/api/views.py:370 +#: awx/api/views.py:372 msgid "Dashboard" msgstr "Panel de control" -#: awx/api/views.py:469 +#: awx/api/views.py:471 msgid "Dashboard Jobs Graphs" msgstr "Panel de control de gráficas de trabajo" -#: awx/api/views.py:505 +#: awx/api/views.py:507 #, python-format msgid "Unknown period \"%s\"" msgstr "Periodo desconocido \"%s\"" -#: awx/api/views.py:519 +#: awx/api/views.py:521 msgid "Instances" msgstr "Instancias" -#: awx/api/views.py:527 +#: awx/api/views.py:529 msgid "Instance Detail" msgstr "Detalle de la instancia" -#: awx/api/views.py:535 +#: awx/api/views.py:537 msgid "Instance Running Jobs" msgstr "Tareas en ejecución de la instancia" -#: awx/api/views.py:550 +#: awx/api/views.py:552 msgid "Instance's Instance Groups" msgstr "Grupos de instancias de la instancia" -#: awx/api/views.py:560 +#: awx/api/views.py:562 msgid "Instance Groups" msgstr "Grupos de instancias" -#: awx/api/views.py:568 +#: awx/api/views.py:570 msgid "Instance Group Detail" msgstr "Detalle del grupo de instancias" -#: awx/api/views.py:576 +#: awx/api/views.py:578 msgid "Instance Group Running Jobs" msgstr "Tareas en ejecución del grupo de instancias" -#: awx/api/views.py:586 +#: awx/api/views.py:588 msgid "Instance Group's Instances" msgstr "Instancias del grupo de instancias" -#: awx/api/views.py:596 +#: awx/api/views.py:598 msgid "Schedules" msgstr "Programaciones" -#: awx/api/views.py:615 +#: awx/api/views.py:617 msgid "Schedule Jobs List" msgstr "Lista de trabajos programados" -#: awx/api/views.py:841 +#: awx/api/views.py:843 msgid "Your license only permits a single organization to exist." msgstr "Su licencia solo permite que exista una organización." -#: awx/api/views.py:1077 awx/api/views.py:4615 +#: awx/api/views.py:1079 awx/api/views.py:4666 msgid "You cannot assign an Organization role as a child role for a Team." msgstr "No puede asignar un rol de organización como rol hijo para un equipo." -#: awx/api/views.py:1081 awx/api/views.py:4629 +#: awx/api/views.py:1083 awx/api/views.py:4680 msgid "You cannot grant system-level permissions to a team." msgstr "No puede asignar permisos de nivel de sistema a un equipo." -#: awx/api/views.py:1088 awx/api/views.py:4621 +#: awx/api/views.py:1090 awx/api/views.py:4672 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" @@ -758,35 +777,35 @@ msgstr "" "No puede asignar credenciales de acceso a un equipo cuando el campo de " "organización no está establecido o pertenezca a una organización diferente." -#: awx/api/views.py:1178 +#: awx/api/views.py:1180 msgid "Cannot delete project." msgstr "No se puede eliminar el proyecto." -#: awx/api/views.py:1213 +#: awx/api/views.py:1215 msgid "Project Schedules" msgstr "Programación del proyecto" -#: awx/api/views.py:1225 +#: awx/api/views.py:1227 msgid "Project SCM Inventory Sources" msgstr "Fuentes de inventario SCM del proyecto" -#: awx/api/views.py:1352 +#: awx/api/views.py:1354 msgid "Project Update SCM Inventory Updates" msgstr "Actualizaciones de inventario SCM de la actualización del proyecto" -#: awx/api/views.py:1407 +#: awx/api/views.py:1409 msgid "Me" msgstr "Yo" -#: awx/api/views.py:1451 awx/api/views.py:4572 +#: awx/api/views.py:1453 awx/api/views.py:4623 msgid "You may not perform any action with your own admin_role." msgstr "No puede realizar ninguna acción con su admin_role." -#: awx/api/views.py:1457 awx/api/views.py:4576 +#: awx/api/views.py:1459 awx/api/views.py:4627 msgid "You may not change the membership of a users admin_role" msgstr "No puede cambiar la pertenencia a usuarios admin_role" -#: awx/api/views.py:1462 awx/api/views.py:4581 +#: awx/api/views.py:1464 awx/api/views.py:4632 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" @@ -794,53 +813,54 @@ msgstr "" "No puede conceder credenciales de acceso a un usuario que no está en la " "organización del credencial." -#: awx/api/views.py:1466 awx/api/views.py:4585 +#: awx/api/views.py:1468 awx/api/views.py:4636 msgid "You cannot grant private credential access to another user" msgstr "No puede conceder acceso a un credencial privado a otro usuario" -#: awx/api/views.py:1564 +#: awx/api/views.py:1566 #, python-format msgid "Cannot change %s." msgstr "No se puede cambiar %s." -#: awx/api/views.py:1570 +#: awx/api/views.py:1572 msgid "Cannot delete user." msgstr "No se puede eliminar usuario." -#: awx/api/views.py:1599 +#: awx/api/views.py:1601 msgid "Deletion not allowed for managed credential types" msgstr "" "No se permite la eliminación para los tipos de credenciales administradas" -#: awx/api/views.py:1601 +#: awx/api/views.py:1603 msgid "Credential types that are in use cannot be deleted" msgstr "No se pueden eliminar los tipos de credenciales en uso" -#: awx/api/views.py:1779 +#: awx/api/views.py:1781 msgid "Cannot delete inventory script." msgstr "No se puede eliminar script de inventario." -#: awx/api/views.py:1864 +#: awx/api/views.py:1866 +#, python-brace-format msgid "{0}" msgstr "{0}" -#: awx/api/views.py:2089 +#: awx/api/views.py:2101 msgid "Fact not found." msgstr "Fact no encontrado." -#: awx/api/views.py:2113 +#: awx/api/views.py:2125 msgid "SSLError while trying to connect to {}" msgstr "SSLError al intentar conectarse a {}" -#: awx/api/views.py:2115 +#: awx/api/views.py:2127 msgid "Request to {} timed out." msgstr "El tiempo de solicitud {} caducó." -#: awx/api/views.py:2117 +#: awx/api/views.py:2129 msgid "Unkown exception {} while trying to GET {}" msgstr "Excepción desconocida {} al intentar OBTENER {}" -#: awx/api/views.py:2120 +#: awx/api/views.py:2132 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." @@ -848,7 +868,7 @@ msgstr "" "Acceso no autorizado. Verifique su nombre de usuario y contraseña de " "Insights." -#: awx/api/views.py:2122 +#: awx/api/views.py:2134 msgid "" "Failed to gather reports and maintenance plans from Insights API at URL {}. " "Server responded with {} status code and message {}" @@ -857,202 +877,212 @@ msgstr "" " de Insights en la dirección URL {}. El servidor respondió con el código de " "estado {} y el mensaje {}" -#: awx/api/views.py:2128 +#: awx/api/views.py:2140 msgid "Expected JSON response from Insights but instead got {}" msgstr "Respuesta JSON esperada de Insights; en cambio, se recibió {}" -#: awx/api/views.py:2135 +#: awx/api/views.py:2147 msgid "This host is not recognized as an Insights host." msgstr "Este host no se reconoce como un host de Insights." -#: awx/api/views.py:2140 +#: awx/api/views.py:2152 msgid "The Insights Credential for \"{}\" was not found." msgstr "No se encontró la credencial de Insights para \"{}\"." -#: awx/api/views.py:2209 +#: awx/api/views.py:2221 msgid "Cyclical Group association." msgstr "Asociación de grupos cíclica." -#: awx/api/views.py:2482 +#: awx/api/views.py:2499 msgid "Inventory Source List" msgstr "Listado de fuentes del inventario" -#: awx/api/views.py:2495 +#: awx/api/views.py:2512 msgid "Inventory Sources Update" msgstr "Actualización de fuentes de inventario" -#: awx/api/views.py:2525 +#: awx/api/views.py:2542 msgid "Could not start because `can_update` returned False" msgstr "No se pudo iniciar porque `can_update` devolvió False" -#: awx/api/views.py:2533 +#: awx/api/views.py:2550 msgid "No inventory sources to update." msgstr "No hay fuentes de inventario para actualizar." -#: awx/api/views.py:2565 +#: awx/api/views.py:2582 msgid "Cannot delete inventory source." msgstr "No se puede eliminar la fuente del inventario." -#: awx/api/views.py:2573 +#: awx/api/views.py:2590 msgid "Inventory Source Schedules" msgstr "Programaciones de la fuente del inventario" -#: awx/api/views.py:2603 +#: awx/api/views.py:2620 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" "Plantillas de notificación pueden ser sólo asignadas cuando la fuente es una" " de estas {}." -#: awx/api/views.py:2826 +#: awx/api/views.py:2851 msgid "Job Template Schedules" msgstr "Programación plantilla de trabajo" -#: awx/api/views.py:2846 awx/api/views.py:2862 +#: awx/api/views.py:2871 awx/api/views.py:2882 msgid "Your license does not allow adding surveys." msgstr "Su licencia no permite añadir cuestionarios." -#: awx/api/views.py:2869 +#: awx/api/views.py:2889 msgid "'name' missing from survey spec." msgstr "'name' no encontrado en el especificado cuestionario." -#: awx/api/views.py:2871 +#: awx/api/views.py:2891 msgid "'description' missing from survey spec." msgstr "'description' no encontrado en el especificado cuestionario." -#: awx/api/views.py:2873 +#: awx/api/views.py:2893 msgid "'spec' missing from survey spec." msgstr "'spec' no encontrado en el especificado cuestionario." -#: awx/api/views.py:2875 +#: awx/api/views.py:2895 msgid "'spec' must be a list of items." msgstr "'spec' debe ser una lista de elementos." -#: awx/api/views.py:2877 +#: awx/api/views.py:2897 msgid "'spec' doesn't contain any items." msgstr "'spec' no contiene ningún elemento." -#: awx/api/views.py:2883 +#: awx/api/views.py:2903 #, python-format msgid "Survey question %s is not a json object." msgstr "Pregunta de cuestionario %s no es un objeto JSON." -#: awx/api/views.py:2885 +#: awx/api/views.py:2905 #, python-format msgid "'type' missing from survey question %s." msgstr "'type' no encontrado en la pregunta de cuestionario %s." -#: awx/api/views.py:2887 +#: awx/api/views.py:2907 #, python-format msgid "'question_name' missing from survey question %s." msgstr "'question_name' no aparece en la pregunta de cuestionario %s." -#: awx/api/views.py:2889 +#: awx/api/views.py:2909 #, python-format msgid "'variable' missing from survey question %s." msgstr "'variable' no encontrado en la pregunta de cuestionario %s." -#: awx/api/views.py:2891 +#: awx/api/views.py:2911 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "" "'variable' '%(item)s' repetida en la pregunta de cuestionario %(survey)s." -#: awx/api/views.py:2896 +#: awx/api/views.py:2916 #, python-format msgid "'required' missing from survey question %s." msgstr "'required' no encontrado en la pregunta de cuestionario %s." -#: awx/api/views.py:2901 +#: awx/api/views.py:2921 +#, python-brace-format msgid "" -"$encrypted$ is reserved keyword and may not be used as a default for " -"password {}." +"Value {question_default} for '{variable_name}' expected to be a string." msgstr "" -"$encrypted$ es una palabra clave reservada y no puede utilizarse como un " -"valor predeterminado para la contraseña {}." -#: awx/api/views.py:3017 +#: awx/api/views.py:2928 +#, python-brace-format +msgid "" +"$encrypted$ is reserved keyword for password questions and may not be used " +"as a default for '{variable_name}' in survey question {question_position}." +msgstr "" + +#: awx/api/views.py:3049 msgid "Maximum number of labels for {} reached." msgstr "Número máximo de etiquetas para {} alcanzado." -#: awx/api/views.py:3136 +#: awx/api/views.py:3170 msgid "No matching host could be found!" msgstr "¡Ningún servidor indicado pudo ser encontrado!" -#: awx/api/views.py:3139 +#: awx/api/views.py:3173 msgid "Multiple hosts matched the request!" msgstr "¡Varios servidores corresponden a la petición!" -#: awx/api/views.py:3144 +#: awx/api/views.py:3178 msgid "Cannot start automatically, user input required!" msgstr "" "No se puede iniciar automáticamente, !Entrada de datos de usuario necesaria!" -#: awx/api/views.py:3151 +#: awx/api/views.py:3185 msgid "Host callback job already pending." msgstr "Trabajo de callback para el servidor ya está pendiente." -#: awx/api/views.py:3164 +#: awx/api/views.py:3199 msgid "Error starting job!" msgstr "¡Error iniciando trabajo!" -#: awx/api/views.py:3271 +#: awx/api/views.py:3306 +#, python-brace-format msgid "Cannot associate {0} when {1} have been associated." msgstr "No se puede asociar {0} cuando se ha asociado {1}." -#: awx/api/views.py:3296 +#: awx/api/views.py:3331 msgid "Multiple parent relationship not allowed." msgstr "No se permiten múltiples relaciones primarias." -#: awx/api/views.py:3301 +#: awx/api/views.py:3336 msgid "Cycle detected." msgstr "Ciclo detectado." -#: awx/api/views.py:3505 +#: awx/api/views.py:3540 msgid "Workflow Job Template Schedules" msgstr "Programaciones de plantilla de tareas de flujo de trabajo" -#: awx/api/views.py:3650 awx/api/views.py:4217 +#: awx/api/views.py:3685 awx/api/views.py:4268 msgid "Superuser privileges needed." msgstr "Privilegios de superusuario necesarios." -#: awx/api/views.py:3682 +#: awx/api/views.py:3717 msgid "System Job Template Schedules" msgstr "Programación de plantilla de trabajos de sistema." -#: awx/api/views.py:3891 +#: awx/api/views.py:3780 +msgid "POST not allowed for Job launching in version 2 of the api" +msgstr "" + +#: awx/api/views.py:3942 msgid "Job Host Summaries List" msgstr "Lista resumida de trabajos de servidor" -#: awx/api/views.py:3938 +#: awx/api/views.py:3989 msgid "Job Event Children List" msgstr "LIsta de hijos de eventos de trabajo" -#: awx/api/views.py:3947 +#: awx/api/views.py:3998 msgid "Job Event Hosts List" msgstr "Lista de eventos de trabajos de servidor." -#: awx/api/views.py:3957 +#: awx/api/views.py:4008 msgid "Job Events List" msgstr "Lista de eventos de trabajo" -#: awx/api/views.py:4171 +#: awx/api/views.py:4222 msgid "Ad Hoc Command Events List" msgstr "Lista de eventos para comando Ad Hoc" -#: awx/api/views.py:4386 +#: awx/api/views.py:4437 msgid "Error generating stdout download file: {}" msgstr "Error generando la descarga del fichero de salida estándar: {}" -#: awx/api/views.py:4399 +#: awx/api/views.py:4450 #, python-format msgid "Error generating stdout download file: %s" msgstr "Error generando la descarga del fichero de salida estándar: %s" -#: awx/api/views.py:4444 +#: awx/api/views.py:4495 msgid "Delete not allowed while there are pending notifications" msgstr "Eliminar no está permitido mientras existan notificaciones pendientes" -#: awx/api/views.py:4451 +#: awx/api/views.py:4502 msgid "Notification Template Test" msgstr "Prueba de plantilla de notificación" @@ -1210,15 +1240,16 @@ msgstr "Ejemplo de configuración que puede ser diferente para cada usuario." msgid "User" msgstr "Usuario" -#: awx/conf/fields.py:62 +#: awx/conf/fields.py:63 msgid "Enter a valid URL" msgstr "Introduzca una URL válida" -#: awx/conf/fields.py:94 +#: awx/conf/fields.py:95 +#, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input}\" no es una cadena válida." -#: awx/conf/license.py:19 +#: awx/conf/license.py:22 msgid "Your Tower license does not allow that." msgstr "Su licencia Tower no permite eso." @@ -1235,7 +1266,11 @@ msgstr "Omitir los ajustes que causarán un error cuando comentando/migrando." msgid "Skip commenting out settings in files." msgstr "Omitir la entrada de comentarios para ajustes en ficheros." -#: awx/conf/management/commands/migrate_to_database_settings.py:61 +#: awx/conf/management/commands/migrate_to_database_settings.py:62 +msgid "Skip migrating and only comment out settings in files." +msgstr "" + +#: awx/conf/management/commands/migrate_to_database_settings.py:68 msgid "Backup existing settings files with this suffix." msgstr "" "Hacer copia de seguridad de todos los ficheros de configuración existentes " @@ -1261,7 +1296,7 @@ msgstr "Cambiado" msgid "User-Defaults" msgstr "Parámetros de usuario por defecto" -#: awx/conf/registry.py:151 +#: awx/conf/registry.py:154 msgid "This value has been set manually in a settings file." msgstr "" "Este valor ha sido establecido manualmente en el fichero de configuración." @@ -1334,94 +1369,92 @@ msgstr "Detalles del ajuste" msgid "Logging Connectivity Test" msgstr "Registrando prueba de conectividad" -#: awx/main/access.py:224 +#: awx/main/access.py:44 +msgid "Resource is being used by running jobs." +msgstr "" + +#: awx/main/access.py:237 #, python-format msgid "Bad data found in related field %s." msgstr "Dato incorrecto encontrado en el campo relacionado %s." -#: awx/main/access.py:268 +#: awx/main/access.py:281 msgid "License is missing." msgstr "Licencia no encontrada." -#: awx/main/access.py:270 +#: awx/main/access.py:283 msgid "License has expired." msgstr "La licencia ha expirado." -#: awx/main/access.py:278 +#: awx/main/access.py:291 #, python-format msgid "License count of %s instances has been reached." msgstr "El número de licencias de instancias %s ha sido alcanzado." -#: awx/main/access.py:280 +#: awx/main/access.py:293 #, python-format msgid "License count of %s instances has been exceeded." msgstr "El número de licencias de instancias %s ha sido excedido." -#: awx/main/access.py:282 +#: awx/main/access.py:295 msgid "Host count exceeds available instances." msgstr "El número de servidores excede las instancias disponibles." -#: awx/main/access.py:286 +#: awx/main/access.py:299 #, python-format msgid "Feature %s is not enabled in the active license." msgstr "Funcionalidad %s no está habilitada en la licencia activa." -#: awx/main/access.py:288 +#: awx/main/access.py:301 msgid "Features not found in active license." msgstr "Funcionalidades no encontradas en la licencia activa." -#: awx/main/access.py:534 awx/main/access.py:619 awx/main/access.py:748 -#: awx/main/access.py:801 awx/main/access.py:1063 awx/main/access.py:1263 -#: awx/main/access.py:1725 -msgid "Resource is being used by running jobs" -msgstr "Recurso está siendo usado por trabajos en ejecución" - -#: awx/main/access.py:675 +#: awx/main/access.py:707 msgid "Unable to change inventory on a host." msgstr "Imposible modificar el inventario en un servidor." -#: awx/main/access.py:692 awx/main/access.py:737 +#: awx/main/access.py:724 awx/main/access.py:769 msgid "Cannot associate two items from different inventories." msgstr "No es posible asociar dos elementos de diferentes inventarios." -#: awx/main/access.py:725 +#: awx/main/access.py:757 msgid "Unable to change inventory on a group." msgstr "Imposible cambiar el inventario en un grupo." -#: awx/main/access.py:983 +#: awx/main/access.py:1017 msgid "Unable to change organization on a team." msgstr "Imposible cambiar la organización en un equipo." -#: awx/main/access.py:996 +#: awx/main/access.py:1030 msgid "The {} role cannot be assigned to a team" msgstr "El rol {} no puede ser asignado a un equipo." -#: awx/main/access.py:998 +#: awx/main/access.py:1032 msgid "The admin_role for a User cannot be assigned to a team" msgstr "El admin_role para un usuario no puede ser asignado a un equipo" -#: awx/main/access.py:1443 +#: awx/main/access.py:1479 msgid "Job has been orphaned from its job template." msgstr "Se ha eliminado la plantilla de trabajo de la tarea." -#: awx/main/access.py:1445 +#: awx/main/access.py:1481 msgid "You do not have execute permission to related job template." msgstr "" "No tiene permiso de ejecución para la plantilla de trabajo relacionada." -#: awx/main/access.py:1448 +#: awx/main/access.py:1484 msgid "Job was launched with prompted fields." msgstr "La tarea se ejecutó con campos completados." -#: awx/main/access.py:1450 +#: awx/main/access.py:1486 msgid " Organization level permissions required." msgstr "Se requieren permisos de nivel de organización." -#: awx/main/access.py:1452 +#: awx/main/access.py:1488 msgid " You do not have permission to related resources." msgstr "No tiene permisos para los recursos relacionados." -#: awx/main/access.py:1798 +#: awx/main/access.py:1833 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." @@ -1962,85 +1995,85 @@ msgstr "" msgid "\n" msgstr "\n" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Sudo" msgstr "Sudo" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Su" msgstr "Su" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pbrun" msgstr "Pbrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pfexec" msgstr "Pfexec" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "DZDO" msgstr "DZDO" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pmrun" msgstr "Pmrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Runas" msgstr "Runas" -#: awx/main/fields.py:56 +#: awx/main/fields.py:57 #, python-format msgid "'%s' is not one of ['%s']" msgstr "'%s' no es uno de ['%s']" -#: awx/main/fields.py:531 +#: awx/main/fields.py:533 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "no puede establecerse excepto que se defina \"%s\"" -#: awx/main/fields.py:547 +#: awx/main/fields.py:549 #, python-format msgid "required for %s" msgstr "requerido para %s" -#: awx/main/fields.py:571 +#: awx/main/fields.py:573 msgid "must be set when SSH key is encrypted." msgstr "se debe establecer cuando la clave SSH está cifrada." -#: awx/main/fields.py:577 +#: awx/main/fields.py:579 msgid "should not be set when SSH key is not encrypted." msgstr "no se debe establecer cuando la clave SSH no está cifrada." -#: awx/main/fields.py:635 +#: awx/main/fields.py:637 msgid "'dependencies' is not supported for custom credentials." msgstr "'dependencias' no es compatible con las credenciales personalizadas." -#: awx/main/fields.py:649 +#: awx/main/fields.py:651 msgid "\"tower\" is a reserved field name" msgstr "\"tower\" es un nombre de campo reservado" -#: awx/main/fields.py:656 +#: awx/main/fields.py:658 #, python-format msgid "field IDs must be unique (%s)" msgstr "los ID de campo deben ser únicos (%s)" -#: awx/main/fields.py:669 +#: awx/main/fields.py:671 #, python-format msgid "%s not allowed for %s type (%s)" msgstr "%s no está permitido para el tipo %s (%s)" -#: awx/main/fields.py:753 +#: awx/main/fields.py:755 #, python-format msgid "%s uses an undefined field (%s)" msgstr "%s usa un campo indefinido (%s)" -#: awx/main/middleware.py:121 +#: awx/main/middleware.py:157 msgid "Formats of all available named urls" msgstr "Formatos de todas las URL con nombre disponibles" -#: awx/main/middleware.py:122 +#: awx/main/middleware.py:158 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." @@ -2048,15 +2081,15 @@ msgstr "" "Lista de solo lectura de los pares clave-valor que muestra el formato " "estándar de todas las URL con nombre disponibles." -#: awx/main/middleware.py:124 awx/main/middleware.py:134 +#: awx/main/middleware.py:160 awx/main/middleware.py:170 msgid "Named URL" msgstr "URL con nombre" -#: awx/main/middleware.py:131 +#: awx/main/middleware.py:167 msgid "List of all named url graph nodes." msgstr "Lista de todos los nodos gráficos de URL con nombre." -#: awx/main/middleware.py:132 +#: awx/main/middleware.py:168 msgid "" "Read-only list of key-value pairs that exposes named URL graph topology. Use" " this list to programmatically generate named URLs for resources" @@ -2065,51 +2098,51 @@ msgstr "" "gráfica de URL con nombre. Use esta lista para generar URL con nombre para " "recursos mediante programación." -#: awx/main/migrations/_reencrypt.py:23 awx/main/models/notifications.py:33 +#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:33 msgid "Email" msgstr "Correo electrónico" -#: awx/main/migrations/_reencrypt.py:24 awx/main/models/notifications.py:34 +#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:34 msgid "Slack" msgstr "Slack" -#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:35 +#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:35 msgid "Twilio" msgstr "Twilio" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:36 +#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:36 msgid "Pagerduty" msgstr "Pagerduty" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:37 +#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:37 msgid "HipChat" msgstr "HipChat" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:38 +#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:38 msgid "Webhook" msgstr "Webhook" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:39 +#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:39 msgid "IRC" msgstr "IRC" -#: awx/main/models/activity_stream.py:24 +#: awx/main/models/activity_stream.py:25 msgid "Entity Created" msgstr "Entidad creada" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:26 msgid "Entity Updated" msgstr "Entidad actualizada" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:27 msgid "Entity Deleted" msgstr "Entidad eliminada" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:28 msgid "Entity Associated with another Entity" msgstr "Entidad asociada con otra entidad" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:29 msgid "Entity was Disassociated with another Entity" msgstr "La entidad fue desasociada de otra entidad" @@ -2135,43 +2168,43 @@ msgstr "Módulo no soportado para comandos ad hoc." msgid "No argument passed to %s module." msgstr "Ningún argumento pasado al módulo %s." -#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:904 +#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:911 msgid "Host Failed" msgstr "Servidor fallido" -#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:905 +#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:912 msgid "Host OK" msgstr "Servidor OK" -#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:908 +#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:915 msgid "Host Unreachable" msgstr "Servidor no alcanzable" -#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:907 +#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:914 msgid "Host Skipped" msgstr "Servidor omitido" -#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:935 +#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:942 msgid "Debug" msgstr "Debug" -#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:936 +#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:943 msgid "Verbose" msgstr "Nivel de detalle" -#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:937 +#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:944 msgid "Deprecated" msgstr "Obsoleto" -#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:938 +#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:945 msgid "Warning" msgstr "Advertencia" -#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:939 +#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:946 msgid "System Warning" msgstr "Advertencia del sistema" -#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:940 +#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:947 #: awx/main/models/unified_jobs.py:64 msgid "Error" msgstr "Error" @@ -2190,27 +2223,27 @@ msgstr "Comprobar" msgid "Scan" msgstr "Escanear" -#: awx/main/models/credential.py:82 +#: awx/main/models/credential.py:86 msgid "Host" msgstr "Servidor" -#: awx/main/models/credential.py:83 +#: awx/main/models/credential.py:87 msgid "The hostname or IP address to use." msgstr "El hostname o dirección IP a utilizar." -#: awx/main/models/credential.py:89 +#: awx/main/models/credential.py:93 msgid "Username" msgstr "Usuario" -#: awx/main/models/credential.py:90 +#: awx/main/models/credential.py:94 msgid "Username for this credential." msgstr "Usuario para este credencial" -#: awx/main/models/credential.py:96 +#: awx/main/models/credential.py:100 msgid "Password" msgstr "Contraseña" -#: awx/main/models/credential.py:97 +#: awx/main/models/credential.py:101 msgid "" "Password for this credential (or \"ASK\" to prompt the user for machine " "credentials)." @@ -2218,43 +2251,43 @@ msgstr "" "Contraseña para este credencial (o \"ASK\" para solicitar al usuario por " "credenciales de máquina)." -#: awx/main/models/credential.py:104 +#: awx/main/models/credential.py:108 msgid "Security Token" msgstr "Token de seguridad" -#: awx/main/models/credential.py:105 +#: awx/main/models/credential.py:109 msgid "Security Token for this credential" msgstr "Token de seguridad para este credencial" -#: awx/main/models/credential.py:111 +#: awx/main/models/credential.py:115 msgid "Project" msgstr "Proyecto" -#: awx/main/models/credential.py:112 +#: awx/main/models/credential.py:116 msgid "The identifier for the project." msgstr "El identificador para el proyecto" -#: awx/main/models/credential.py:118 +#: awx/main/models/credential.py:122 msgid "Domain" msgstr "Dominio" -#: awx/main/models/credential.py:119 +#: awx/main/models/credential.py:123 msgid "The identifier for the domain." msgstr "El identificador para el dominio." -#: awx/main/models/credential.py:124 +#: awx/main/models/credential.py:128 msgid "SSH private key" msgstr "Clave privada SSH" -#: awx/main/models/credential.py:125 +#: awx/main/models/credential.py:129 msgid "RSA or DSA private key to be used instead of password." msgstr "Clave privada RSA o DSA a ser utilizada en vez de una contraseña." -#: awx/main/models/credential.py:131 +#: awx/main/models/credential.py:135 msgid "SSH key unlock" msgstr "Desbloquear clave SSH" -#: awx/main/models/credential.py:132 +#: awx/main/models/credential.py:136 msgid "" "Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " "user for machine credentials)." @@ -2262,51 +2295,51 @@ msgstr "" "Frase de contraseña para desbloquear la clave privada SSH si está cifrada (o" " \"ASK\" para solicitar al usuario por credenciales de máquina)." -#: awx/main/models/credential.py:139 +#: awx/main/models/credential.py:143 msgid "None" msgstr "Ninguno" -#: awx/main/models/credential.py:140 +#: awx/main/models/credential.py:144 msgid "Privilege escalation method." msgstr "Método de elevación de privilegios." -#: awx/main/models/credential.py:146 +#: awx/main/models/credential.py:150 msgid "Privilege escalation username." msgstr "Usuario para la elevación de privilegios." -#: awx/main/models/credential.py:152 +#: awx/main/models/credential.py:156 msgid "Password for privilege escalation method." msgstr "Contraseña para el método de elevación de privilegios" -#: awx/main/models/credential.py:158 +#: awx/main/models/credential.py:162 msgid "Vault password (or \"ASK\" to prompt the user)." msgstr "Contraseña de Vault (o \"ASK\" para solicitar al usuario)." -#: awx/main/models/credential.py:162 +#: awx/main/models/credential.py:166 msgid "Whether to use the authorize mechanism." msgstr "Si se utilizará el mecanismo de autentificación." -#: awx/main/models/credential.py:168 +#: awx/main/models/credential.py:172 msgid "Password used by the authorize mechanism." msgstr "Contraseña utilizada para el mecanismo de autentificación." -#: awx/main/models/credential.py:174 +#: awx/main/models/credential.py:178 msgid "Client Id or Application Id for the credential" msgstr "Id del cliente o Id de aplicación para el credencial" -#: awx/main/models/credential.py:180 +#: awx/main/models/credential.py:184 msgid "Secret Token for this credential" msgstr "Token secreto para este credencial" -#: awx/main/models/credential.py:186 +#: awx/main/models/credential.py:190 msgid "Subscription identifier for this credential" msgstr "Identificador de suscripción para este credencial" -#: awx/main/models/credential.py:192 +#: awx/main/models/credential.py:196 msgid "Tenant identifier for this credential" msgstr "Identificador de inquilino [Tenant] para este credencial" -#: awx/main/models/credential.py:216 +#: awx/main/models/credential.py:220 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." @@ -2314,7 +2347,7 @@ msgstr "" "Especifique el tipo de credencial que desea crear. Consulte la documentación" " de Ansible Tower para obtener información sobre cada tipo." -#: awx/main/models/credential.py:230 awx/main/models/credential.py:416 +#: awx/main/models/credential.py:234 awx/main/models/credential.py:420 msgid "" "Enter inputs using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2324,31 +2357,31 @@ msgstr "" "selección para alternar entre las dos opciones. Consulte la documentación de" " Ansible Tower para ver sintaxis de ejemplo." -#: awx/main/models/credential.py:397 +#: awx/main/models/credential.py:401 msgid "Machine" msgstr "Máquina" -#: awx/main/models/credential.py:398 +#: awx/main/models/credential.py:402 msgid "Vault" msgstr "Vault" -#: awx/main/models/credential.py:399 +#: awx/main/models/credential.py:403 msgid "Network" msgstr "Red" -#: awx/main/models/credential.py:400 +#: awx/main/models/credential.py:404 msgid "Source Control" msgstr "Fuente de control" -#: awx/main/models/credential.py:401 +#: awx/main/models/credential.py:405 msgid "Cloud" msgstr "Nube" -#: awx/main/models/credential.py:402 +#: awx/main/models/credential.py:406 msgid "Insights" msgstr "Insights" -#: awx/main/models/credential.py:423 +#: awx/main/models/credential.py:427 msgid "" "Enter injectors using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2358,6 +2391,11 @@ msgstr "" "selección para alternar entre las dos opciones. Consulte la documentación de" " Ansible Tower para ver sintaxis de ejemplo." +#: awx/main/models/credential.py:478 +#, python-format +msgid "adding %s credential type" +msgstr "" + #: awx/main/models/fact.py:25 msgid "Host for the facts that the fact scan captured." msgstr "Servidor para los facts que el escaneo de facts capture." @@ -2376,11 +2414,11 @@ msgstr "" "Estructura de JSON arbitraria de módulos facts capturados en la fecha y hora" " para un único servidor." -#: awx/main/models/ha.py:76 +#: awx/main/models/ha.py:78 msgid "Instances that are members of this InstanceGroup" msgstr "Las instancias que son miembros de este grupo de instancias" -#: awx/main/models/ha.py:81 +#: awx/main/models/ha.py:83 msgid "Instance Group to remotely control this group." msgstr "Grupo de instancias para controlar remotamente este grupo." @@ -2582,38 +2620,42 @@ msgid "Google Compute Engine" msgstr "Google Compute Engine" #: awx/main/models/inventory.py:870 -msgid "Microsoft Azure Classic (deprecated)" -msgstr "Microsoft Azure Classic (obsoleto)" - -#: awx/main/models/inventory.py:871 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/inventory.py:872 +#: awx/main/models/inventory.py:871 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/inventory.py:873 +#: awx/main/models/inventory.py:872 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/inventory.py:874 +#: awx/main/models/inventory.py:873 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/inventory.py:875 +#: awx/main/models/inventory.py:874 msgid "OpenStack" msgstr "OpenStack" +#: awx/main/models/inventory.py:875 +msgid "oVirt4" +msgstr "" + #: awx/main/models/inventory.py:876 +msgid "Ansible Tower" +msgstr "Ansible Tower" + +#: awx/main/models/inventory.py:877 msgid "Custom Script" msgstr "Script personalizado" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:994 msgid "Inventory source variables in YAML or JSON format." msgstr "Variables para la fuente del inventario en formato YAML o JSON." -#: awx/main/models/inventory.py:1012 +#: awx/main/models/inventory.py:1013 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." @@ -2621,79 +2663,79 @@ msgstr "" "Lista de expresiones de filtrado separadas por coma (sólo EC2). Servidores " "son importados cuando ALGÚN filtro coincide." -#: awx/main/models/inventory.py:1018 +#: awx/main/models/inventory.py:1019 msgid "Limit groups automatically created from inventory source (EC2 only)." msgstr "" "Limitar grupos creados automáticamente desde la fuente del inventario (sólo " "EC2)" -#: awx/main/models/inventory.py:1022 +#: awx/main/models/inventory.py:1023 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" "Sobrescribir grupos locales y servidores desde una fuente remota del " "inventario." -#: awx/main/models/inventory.py:1026 +#: awx/main/models/inventory.py:1027 msgid "Overwrite local variables from remote inventory source." msgstr "" "Sobrescribir las variables locales desde una fuente remota del inventario." -#: awx/main/models/inventory.py:1031 awx/main/models/jobs.py:159 +#: awx/main/models/inventory.py:1032 awx/main/models/jobs.py:160 #: awx/main/models/projects.py:117 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "" "La cantidad de tiempo (en segundos) para ejecutar antes de que se cancele la" " tarea." -#: awx/main/models/inventory.py:1064 +#: awx/main/models/inventory.py:1065 msgid "Image ID" msgstr "Id de imagen" -#: awx/main/models/inventory.py:1065 +#: awx/main/models/inventory.py:1066 msgid "Availability Zone" msgstr "Zona de disponibilidad" -#: awx/main/models/inventory.py:1066 +#: awx/main/models/inventory.py:1067 msgid "Account" msgstr "Cuenta" -#: awx/main/models/inventory.py:1067 +#: awx/main/models/inventory.py:1068 msgid "Instance ID" msgstr "ID de instancia" -#: awx/main/models/inventory.py:1068 +#: awx/main/models/inventory.py:1069 msgid "Instance State" msgstr "Estado de la instancia" -#: awx/main/models/inventory.py:1069 +#: awx/main/models/inventory.py:1070 msgid "Instance Type" msgstr "Tipo de instancia" -#: awx/main/models/inventory.py:1070 +#: awx/main/models/inventory.py:1071 msgid "Key Name" msgstr "Nombre clave" -#: awx/main/models/inventory.py:1071 +#: awx/main/models/inventory.py:1072 msgid "Region" msgstr "Región" -#: awx/main/models/inventory.py:1072 +#: awx/main/models/inventory.py:1073 msgid "Security Group" msgstr "Grupo de seguridad" -#: awx/main/models/inventory.py:1073 +#: awx/main/models/inventory.py:1074 msgid "Tags" msgstr "Etiquetas" -#: awx/main/models/inventory.py:1074 +#: awx/main/models/inventory.py:1075 msgid "Tag None" msgstr "Etiqueta ninguna" -#: awx/main/models/inventory.py:1075 +#: awx/main/models/inventory.py:1076 msgid "VPC ID" msgstr "VPC ID" -#: awx/main/models/inventory.py:1138 +#: awx/main/models/inventory.py:1145 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " @@ -2702,30 +2744,36 @@ msgstr "" "Fuentes de inventario basados en Cloud (como %s) requieren credenciales para" " identificar los correspondientes servicios cloud." -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:1152 msgid "Credential is required for a cloud source." msgstr "Un credencial es necesario para una fuente cloud." -#: awx/main/models/inventory.py:1167 +#: awx/main/models/inventory.py:1155 +msgid "" +"Credentials of type machine, source control, insights and vault are " +"disallowed for custom inventory sources." +msgstr "" + +#: awx/main/models/inventory.py:1179 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "Región %(source)s inválida: %(region)s" -#: awx/main/models/inventory.py:1191 +#: awx/main/models/inventory.py:1203 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "Expresión de filtro inválida: %(filter)s" -#: awx/main/models/inventory.py:1212 +#: awx/main/models/inventory.py:1224 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "Grupo escogido inválido: %(choice)s" -#: awx/main/models/inventory.py:1247 +#: awx/main/models/inventory.py:1259 msgid "Project containing inventory file used as source." msgstr "Proyecto que contiene el archivo de inventario usado como fuente." -#: awx/main/models/inventory.py:1395 +#: awx/main/models/inventory.py:1407 #, python-format msgid "" "Unable to configure this item for cloud sync. It is already managed by %s." @@ -2733,7 +2781,7 @@ msgstr "" "Imposible configurar este elemento para sincronización cloud. Está " "administrado actualmente por %s." -#: awx/main/models/inventory.py:1405 +#: awx/main/models/inventory.py:1417 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." @@ -2741,7 +2789,7 @@ msgstr "" "No se permite más de una fuente de inventario basada en SCM con " "actualización en la actualización del proyecto por inventario." -#: awx/main/models/inventory.py:1412 +#: awx/main/models/inventory.py:1424 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " @@ -2752,30 +2800,30 @@ msgstr "" "su lugar, configure el proyecto de fuente correspondiente para actualizar en" " la ejecución." -#: awx/main/models/inventory.py:1418 +#: awx/main/models/inventory.py:1430 msgid "SCM type sources must set `overwrite_vars` to `true`." msgstr "Las fuentes de tipo SCM deben configurar `overwrite_vars` en `true`." -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1435 msgid "Cannot set source_path if not SCM type." msgstr "No se puede configurar source_path si no es del tipo SCM." -#: awx/main/models/inventory.py:1448 +#: awx/main/models/inventory.py:1460 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "" "Los archivos de inventario de esta actualización de proyecto se utilizaron " "para la actualización del inventario." -#: awx/main/models/inventory.py:1561 +#: awx/main/models/inventory.py:1573 msgid "Inventory script contents" msgstr "Contenido del script de inventario" -#: awx/main/models/inventory.py:1566 +#: awx/main/models/inventory.py:1578 msgid "Organization owning this inventory script" msgstr "Organización propietario de este script de inventario" -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:66 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" @@ -2783,7 +2831,7 @@ msgstr "" "Si se habilita, los cambios de texto realizados en cualquier archivo de " "plantilla en el host se muestran en la salida estándar" -#: awx/main/models/jobs.py:163 +#: awx/main/models/jobs.py:164 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" " at the end of a playbook run to the database and caching facts for use by " @@ -2794,41 +2842,41 @@ msgstr "" "base de datos y almacenará en caché los eventos que son utilizados por " "Ansible." -#: awx/main/models/jobs.py:172 +#: awx/main/models/jobs.py:173 msgid "You must provide an SSH credential." msgstr "Debe proporcionar una credencial SSH." -#: awx/main/models/jobs.py:180 +#: awx/main/models/jobs.py:181 msgid "You must provide a Vault credential." msgstr "Debe proporcionar una credencial de Vault." -#: awx/main/models/jobs.py:316 +#: awx/main/models/jobs.py:317 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "" "La plantilla de trabajo debe proporcionar 'inventory' o permitir " "solicitarlo." -#: awx/main/models/jobs.py:320 +#: awx/main/models/jobs.py:321 msgid "Job Template must provide 'credential' or allow prompting for it." msgstr "" "La plantilla de trabajo debe proporcionar 'inventory' o permitir " "solicitarlo." -#: awx/main/models/jobs.py:422 +#: awx/main/models/jobs.py:427 msgid "Cannot override job_type to or from a scan job." msgstr "No se puede sustituir job_type a o desde un trabajo de escaneo." -#: awx/main/models/jobs.py:488 awx/main/models/projects.py:263 +#: awx/main/models/jobs.py:493 awx/main/models/projects.py:263 msgid "SCM Revision" msgstr "Revisión SCM" -#: awx/main/models/jobs.py:489 +#: awx/main/models/jobs.py:494 msgid "The SCM Revision from the Project used for this job, if available" msgstr "" "La revisión SCM desde el proyecto usado para este trabajo, si está " "disponible" -#: awx/main/models/jobs.py:497 +#: awx/main/models/jobs.py:502 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" @@ -2836,105 +2884,105 @@ msgstr "" "La tarea de actualización de SCM utilizado para asegurarse que los playbooks" " estaban disponibles para la ejecución del trabajo" -#: awx/main/models/jobs.py:802 +#: awx/main/models/jobs.py:809 msgid "job host summaries" msgstr "Resumen de trabajos de servidor" -#: awx/main/models/jobs.py:906 +#: awx/main/models/jobs.py:913 msgid "Host Failure" msgstr "Fallo del servidor" -#: awx/main/models/jobs.py:909 awx/main/models/jobs.py:923 +#: awx/main/models/jobs.py:916 awx/main/models/jobs.py:930 msgid "No Hosts Remaining" msgstr "No más servidores" -#: awx/main/models/jobs.py:910 +#: awx/main/models/jobs.py:917 msgid "Host Polling" msgstr "Sondeo al servidor" -#: awx/main/models/jobs.py:911 +#: awx/main/models/jobs.py:918 msgid "Host Async OK" msgstr "Servidor Async OK" -#: awx/main/models/jobs.py:912 +#: awx/main/models/jobs.py:919 msgid "Host Async Failure" msgstr "Servidor Async fallido" -#: awx/main/models/jobs.py:913 +#: awx/main/models/jobs.py:920 msgid "Item OK" msgstr "Elemento OK" -#: awx/main/models/jobs.py:914 +#: awx/main/models/jobs.py:921 msgid "Item Failed" msgstr "Elemento fallido" -#: awx/main/models/jobs.py:915 +#: awx/main/models/jobs.py:922 msgid "Item Skipped" msgstr "Elemento omitido" -#: awx/main/models/jobs.py:916 +#: awx/main/models/jobs.py:923 msgid "Host Retry" msgstr "Reintentar servidor" -#: awx/main/models/jobs.py:918 +#: awx/main/models/jobs.py:925 msgid "File Difference" msgstr "Diferencias del fichero" -#: awx/main/models/jobs.py:919 +#: awx/main/models/jobs.py:926 msgid "Playbook Started" msgstr "Playbook iniciado" -#: awx/main/models/jobs.py:920 +#: awx/main/models/jobs.py:927 msgid "Running Handlers" msgstr "Handlers ejecutándose" -#: awx/main/models/jobs.py:921 +#: awx/main/models/jobs.py:928 msgid "Including File" msgstr "Incluyendo fichero" -#: awx/main/models/jobs.py:922 +#: awx/main/models/jobs.py:929 msgid "No Hosts Matched" msgstr "Ningún servidor corresponde" -#: awx/main/models/jobs.py:924 +#: awx/main/models/jobs.py:931 msgid "Task Started" msgstr "Tarea iniciada" -#: awx/main/models/jobs.py:926 +#: awx/main/models/jobs.py:933 msgid "Variables Prompted" msgstr "Variables solicitadas" -#: awx/main/models/jobs.py:927 +#: awx/main/models/jobs.py:934 msgid "Gathering Facts" msgstr "Obteniendo facts" -#: awx/main/models/jobs.py:928 +#: awx/main/models/jobs.py:935 msgid "internal: on Import for Host" msgstr "internal: en la importación para el servidor" -#: awx/main/models/jobs.py:929 +#: awx/main/models/jobs.py:936 msgid "internal: on Not Import for Host" msgstr "internal: en la no importación para el servidor" -#: awx/main/models/jobs.py:930 +#: awx/main/models/jobs.py:937 msgid "Play Started" msgstr "Jugada iniciada" -#: awx/main/models/jobs.py:931 +#: awx/main/models/jobs.py:938 msgid "Playbook Complete" msgstr "Playbook terminado" -#: awx/main/models/jobs.py:1363 +#: awx/main/models/jobs.py:1351 msgid "Remove jobs older than a certain number of days" msgstr "Eliminar trabajos más antiguos que el ńumero de días especificado" -#: awx/main/models/jobs.py:1364 +#: awx/main/models/jobs.py:1352 msgid "Remove activity stream entries older than a certain number of days" msgstr "" "Eliminar entradas del flujo de actividad más antiguos que el número de días " "especificado" -#: awx/main/models/jobs.py:1365 +#: awx/main/models/jobs.py:1353 msgid "Purge and/or reduce the granularity of system tracking data" msgstr "" "Limpiar y/o reducir la granularidad de los datos del sistema de rastreo" @@ -3358,7 +3406,7 @@ msgstr "Excepción conectando a Twilio: {}" msgid "Error sending notification webhook: {}" msgstr "Error enviando notificación weebhook: {}" -#: awx/main/scheduler/__init__.py:184 +#: awx/main/scheduler/task_manager.py:197 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" @@ -3366,7 +3414,7 @@ msgstr "" "Trabajo generado desde un flujo de trabajo no pudo ser iniciado porque no " "tenía el estado correcto o credenciales manuales eran solicitados." -#: awx/main/scheduler/__init__.py:188 +#: awx/main/scheduler/task_manager.py:201 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" @@ -3386,7 +3434,7 @@ msgstr "Licencia de Ansible Tower expirará pronto" msgid "status_str must be either succeeded or failed" msgstr "status_str debe ser 'succeeded' o 'failed'" -#: awx/main/tasks.py:1531 +#: awx/main/tasks.py:1549 msgid "Dependent inventory update {} was canceled." msgstr "Se canceló la actualización del inventario dependiente {}." @@ -3395,38 +3443,38 @@ msgstr "Se canceló la actualización del inventario dependiente {}." msgid "Unable to convert \"%s\" to boolean" msgstr "Imposible convertir \"%s\" a booleano" -#: awx/main/utils/common.py:209 +#: awx/main/utils/common.py:235 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "Tipo de SCM no soportado \"%s\"" -#: awx/main/utils/common.py:216 awx/main/utils/common.py:228 -#: awx/main/utils/common.py:247 +#: awx/main/utils/common.py:242 awx/main/utils/common.py:254 +#: awx/main/utils/common.py:273 #, python-format msgid "Invalid %s URL" msgstr "URL %s inválida" -#: awx/main/utils/common.py:218 awx/main/utils/common.py:257 +#: awx/main/utils/common.py:244 awx/main/utils/common.py:283 #, python-format msgid "Unsupported %s URL" msgstr "URL %s no soportada" -#: awx/main/utils/common.py:259 +#: awx/main/utils/common.py:285 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "Servidor \"%s\" no soportado para URL file://" -#: awx/main/utils/common.py:261 +#: awx/main/utils/common.py:287 #, python-format msgid "Host is required for %s URL" msgstr "Servidor es obligatorio para URL %s" -#: awx/main/utils/common.py:279 +#: awx/main/utils/common.py:305 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "Usuario debe ser \"git\" para acceso SSH a %s." -#: awx/main/utils/common.py:285 +#: awx/main/utils/common.py:311 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "Usuario debe ser \"hg\" para acceso SSH a %s." @@ -3550,287 +3598,287 @@ msgstr "Error de servidor" msgid "A server error has occurred." msgstr "Un error en el servidor ha ocurrido." -#: awx/settings/defaults.py:664 +#: awx/settings/defaults.py:665 msgid "US East (Northern Virginia)" msgstr "Este de EE.UU. (Virginia del norte)" -#: awx/settings/defaults.py:665 +#: awx/settings/defaults.py:666 msgid "US East (Ohio)" msgstr "Este de EE.UU. (Ohio)" -#: awx/settings/defaults.py:666 +#: awx/settings/defaults.py:667 msgid "US West (Oregon)" msgstr "Oeste de EE.UU. (Oregón)" -#: awx/settings/defaults.py:667 +#: awx/settings/defaults.py:668 msgid "US West (Northern California)" msgstr "Oeste de EE.UU (California del norte)" -#: awx/settings/defaults.py:668 +#: awx/settings/defaults.py:669 msgid "Canada (Central)" msgstr "Canada (Central)" -#: awx/settings/defaults.py:669 +#: awx/settings/defaults.py:670 msgid "EU (Frankfurt)" msgstr "UE (Fráncfort)" -#: awx/settings/defaults.py:670 +#: awx/settings/defaults.py:671 msgid "EU (Ireland)" msgstr "UE (Irlanda)" -#: awx/settings/defaults.py:671 +#: awx/settings/defaults.py:672 msgid "EU (London)" msgstr "UE (Londres)" -#: awx/settings/defaults.py:672 +#: awx/settings/defaults.py:673 msgid "Asia Pacific (Singapore)" msgstr "Asia Pacífico (Singapur)" -#: awx/settings/defaults.py:673 +#: awx/settings/defaults.py:674 msgid "Asia Pacific (Sydney)" msgstr "Asia Pacífico (Sídney)" -#: awx/settings/defaults.py:674 +#: awx/settings/defaults.py:675 msgid "Asia Pacific (Tokyo)" msgstr "Asia Pacífico (Tokio)" -#: awx/settings/defaults.py:675 +#: awx/settings/defaults.py:676 msgid "Asia Pacific (Seoul)" msgstr "Asia Pacífico (Seúl)" -#: awx/settings/defaults.py:676 +#: awx/settings/defaults.py:677 msgid "Asia Pacific (Mumbai)" msgstr "Asia Pacífico (Bombay)" -#: awx/settings/defaults.py:677 +#: awx/settings/defaults.py:678 msgid "South America (Sao Paulo)" msgstr "América del sur (São Paulo)" -#: awx/settings/defaults.py:678 +#: awx/settings/defaults.py:679 msgid "US West (GovCloud)" msgstr "Oeste de EE.UU. (GovCloud)" -#: awx/settings/defaults.py:679 +#: awx/settings/defaults.py:680 msgid "China (Beijing)" msgstr "China (Pekín)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:729 msgid "US East 1 (B)" msgstr "Este de EE. UU. 1 (B)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:730 msgid "US East 1 (C)" msgstr "Este de EE. UU. 1 (C)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:731 msgid "US East 1 (D)" msgstr "Este de EE. UU. 1 (D)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:732 msgid "US East 4 (A)" msgstr "Este de EE. UU. 4 (A)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:733 msgid "US East 4 (B)" msgstr "Este de EE. UU. 4 (B)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:734 msgid "US East 4 (C)" msgstr "Este de EE. UU. 4 (C)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:735 msgid "US Central (A)" msgstr "EE.UU. Central (A)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:736 msgid "US Central (B)" msgstr "EE.UU. Central (B)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:737 msgid "US Central (C)" msgstr "EE.UU. Central (C)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:738 msgid "US Central (F)" msgstr "EE.UU. Central (F)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:739 msgid "US West (A)" msgstr "Oeste de EE. UU. (A)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:740 msgid "US West (B)" msgstr "Oeste de EE. UU. (B)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:741 msgid "US West (C)" msgstr "Oeste de EE. UU. (C)" -#: awx/settings/defaults.py:741 +#: awx/settings/defaults.py:742 msgid "Europe West 1 (B)" msgstr "Oeste de Europa 1 (B)" -#: awx/settings/defaults.py:742 +#: awx/settings/defaults.py:743 msgid "Europe West 1 (C)" msgstr "Oeste de Europa 1 (C)" -#: awx/settings/defaults.py:743 +#: awx/settings/defaults.py:744 msgid "Europe West 1 (D)" msgstr "Oeste de Europa 1 (D)" -#: awx/settings/defaults.py:744 +#: awx/settings/defaults.py:745 msgid "Europe West 2 (A)" msgstr "Oeste de Europa 2 (A)" -#: awx/settings/defaults.py:745 +#: awx/settings/defaults.py:746 msgid "Europe West 2 (B)" msgstr "Oeste de Europa 2 (B)" -#: awx/settings/defaults.py:746 +#: awx/settings/defaults.py:747 msgid "Europe West 2 (C)" msgstr "Oeste de Europa 2 (C)" -#: awx/settings/defaults.py:747 +#: awx/settings/defaults.py:748 msgid "Asia East (A)" msgstr "Este de Asia (A)" -#: awx/settings/defaults.py:748 +#: awx/settings/defaults.py:749 msgid "Asia East (B)" msgstr "Este de Asia (B)" -#: awx/settings/defaults.py:749 +#: awx/settings/defaults.py:750 msgid "Asia East (C)" msgstr "Este de Asia (C)" -#: awx/settings/defaults.py:750 +#: awx/settings/defaults.py:751 msgid "Asia Southeast (A)" msgstr "Sudeste de Asia (A)" -#: awx/settings/defaults.py:751 +#: awx/settings/defaults.py:752 msgid "Asia Southeast (B)" msgstr "Sudeste de Asia (B)" -#: awx/settings/defaults.py:752 +#: awx/settings/defaults.py:753 msgid "Asia Northeast (A)" msgstr "Noreste de Asia (A)" -#: awx/settings/defaults.py:753 +#: awx/settings/defaults.py:754 msgid "Asia Northeast (B)" msgstr "Noreste de Asia (B)" -#: awx/settings/defaults.py:754 +#: awx/settings/defaults.py:755 msgid "Asia Northeast (C)" msgstr "Noreste de Asia (C)" -#: awx/settings/defaults.py:755 +#: awx/settings/defaults.py:756 msgid "Australia Southeast (A)" msgstr "Sudeste de Australia (A)" -#: awx/settings/defaults.py:756 +#: awx/settings/defaults.py:757 msgid "Australia Southeast (B)" msgstr "Sudeste de Australia (B)" -#: awx/settings/defaults.py:757 +#: awx/settings/defaults.py:758 msgid "Australia Southeast (C)" msgstr "Sudeste de Australia (C)" -#: awx/settings/defaults.py:781 +#: awx/settings/defaults.py:780 msgid "US East" msgstr "Este de EE.UU." -#: awx/settings/defaults.py:782 +#: awx/settings/defaults.py:781 msgid "US East 2" msgstr "Este de EE.UU. 2" -#: awx/settings/defaults.py:783 +#: awx/settings/defaults.py:782 msgid "US Central" msgstr "EE.UU. Central" -#: awx/settings/defaults.py:784 +#: awx/settings/defaults.py:783 msgid "US North Central" msgstr "Norte-centro de EE.UU." -#: awx/settings/defaults.py:785 +#: awx/settings/defaults.py:784 msgid "US South Central" msgstr "Sur-Centro de EE.UU." -#: awx/settings/defaults.py:786 +#: awx/settings/defaults.py:785 msgid "US West Central" msgstr "Oeste central de EE. UU." -#: awx/settings/defaults.py:787 +#: awx/settings/defaults.py:786 msgid "US West" msgstr "Oeste de EE.UU." -#: awx/settings/defaults.py:788 +#: awx/settings/defaults.py:787 msgid "US West 2" msgstr "Oeste de EE. UU. 2" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:788 msgid "Canada East" msgstr "Este de Canadá" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:789 msgid "Canada Central" msgstr "Canadá Central" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:790 msgid "Brazil South" msgstr "Sur de Brasil" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:791 msgid "Europe North" msgstr "Norte de Europa" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:792 msgid "Europe West" msgstr "Oeste de Europa" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:793 msgid "UK West" msgstr "Oeste del Reino Unido" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:794 msgid "UK South" msgstr "Sur del Reino Unido" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:795 msgid "Asia East" msgstr "Este de Asia" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:796 msgid "Asia Southeast" msgstr "Sudeste de Asia" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:797 msgid "Australia East" msgstr "Este de Australia" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:798 msgid "Australia Southeast" msgstr "Sudeste de Australia" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:799 msgid "India West" msgstr "Oeste de India" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:800 msgid "India South" msgstr "Sur de India" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:801 msgid "Japan East" msgstr "Este de Japón" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:802 msgid "Japan West" msgstr "Oeste de Japón" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:803 msgid "Korea Central" msgstr "Corea central" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:804 msgid "Korea South" msgstr "Sur de Corea" @@ -4593,6 +4641,7 @@ msgid "SAML Team Map" msgstr "Mapa de equipo SAML" #: awx/sso/fields.py:123 +#, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "Opción(es) de conexión inválida(s): {invalid_options}." @@ -4609,17 +4658,20 @@ msgid "Subtree" msgstr "Árbol hijo" #: awx/sso/fields.py:214 +#, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "" "Esperado una lista de tres elementos pero en cambio se proporcionaron " "{length}" #: awx/sso/fields.py:215 +#, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" "Esperado una instancia de LDAPSearch pero en cambio se obtuvo {input_type}" #: awx/sso/fields.py:251 +#, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." @@ -4628,20 +4680,24 @@ msgstr "" "obtuvo {input_type}" #: awx/sso/fields.py:289 +#, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "Atributo(s) de usuario inválido(s): {invalid_attrs}." #: awx/sso/fields.py:306 +#, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" "Se esperaba una instancia de LDAPGroupType pero en cambio se obtuvo " "{input_type}." #: awx/sso/fields.py:334 +#, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "Indicador de usuario inválido: \"{invalid_flag}\"." #: awx/sso/fields.py:350 awx/sso/fields.py:517 +#, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type}" " instead." @@ -4650,50 +4706,61 @@ msgstr "" "texto pero en cambio se obtuvo {input_type}" #: awx/sso/fields.py:386 +#, python-brace-format msgid "Missing key(s): {missing_keys}." msgstr "Clave(s) no encontradas: {missing_keys}." #: awx/sso/fields.py:387 +#, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "Clave(s) inválida(s): {invalid_keys}." #: awx/sso/fields.py:436 awx/sso/fields.py:553 +#, python-brace-format msgid "Invalid key(s) for organization map: {invalid_keys}." msgstr "Clave(s) inválida(s) para map de organización: {invalid_keys}." #: awx/sso/fields.py:454 +#, python-brace-format msgid "Missing required key for team map: {invalid_keys}." msgstr "Clave necesario no encontrada para mapa de equipo: {invalid_keys}." #: awx/sso/fields.py:455 awx/sso/fields.py:572 +#, python-brace-format msgid "Invalid key(s) for team map: {invalid_keys}." msgstr "Clave(s) inválida(s) para mapa de equipo: {invalid_keys}." #: awx/sso/fields.py:571 +#, python-brace-format msgid "Missing required key for team map: {missing_keys}." msgstr "Clave necesaria no encontrada para mapa de equipo: {missing_keys}." #: awx/sso/fields.py:589 +#, python-brace-format msgid "Missing required key(s) for org info record: {missing_keys}." msgstr "" "Clave(s) necesaria(s) no encontrada(s) para el registro informacional de la " "organización: {missing_keys}." #: awx/sso/fields.py:602 +#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "" "Código(s) de lenguaje(s) inválido(s) para información organizacional: " "{invalid_lang_codes}." #: awx/sso/fields.py:621 +#, python-brace-format msgid "Missing required key(s) for contact: {missing_keys}." msgstr "Clave(s) necesaria(s) no encontrada(s) para contacto: {missing_keys}." #: awx/sso/fields.py:633 +#, python-brace-format msgid "Missing required key(s) for IdP: {missing_keys}." msgstr "Clave(s) necesaria(s) no encontrada(s) para IdP: {missing_keys}." #: awx/sso/pipeline.py:24 +#, python-brace-format msgid "An account cannot be found for {0}" msgstr "Una cuenta no puede ser encontrada para {0}" @@ -4787,6 +4854,7 @@ msgid "Make a PATCH request on the %(name)s resource" msgstr "Realizar una petición PATCH en el recurso %(name)s" #: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 +#: awx/ui/conf.py:63 msgid "UI" msgstr "UI" @@ -4841,6 +4909,15 @@ msgstr "" " Para que los logos personalizados se vean mejor, utilice un fichero .png " "con fondo transparente. Formatos GIF, PNG y JPEG están soportados." +#: awx/ui/conf.py:60 +msgid "Max Job Events Retreived by UI" +msgstr "" + +#: awx/ui/conf.py:61 +msgid "" +"Maximum number of job events for the UI to retreive within a single request." +msgstr "" + #: awx/ui/fields.py:29 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " diff --git a/awx/locale/fr/LC_MESSAGES/django.po b/awx/locale/fr/LC_MESSAGES/django.po index f01606b74f..f009e410f4 100644 --- a/awx/locale/fr/LC_MESSAGES/django.po +++ b/awx/locale/fr/LC_MESSAGES/django.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-08-27 19:27+0000\n" -"PO-Revision-Date: 2017-08-28 04:18+0000\n" +"POT-Creation-Date: 2017-11-30 20:23+0000\n" +"PO-Revision-Date: 2017-12-04 03:58+0000\n" "Last-Translator: croe \n" "Language-Team: French\n" "MIME-Version: 1.0\n" @@ -14,7 +14,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" -"X-Generator: Zanata 4.2.1\n" +"X-Generator: Zanata 4.3.2\n" #: awx/api/authentication.py:67 msgid "Invalid token header. No credentials provided." @@ -83,25 +83,30 @@ msgstr "Le filtrage de %s n'est pas autorisé." msgid "Loops not allowed in filters, detected on field {}." msgstr "Boucles non autorisés dans les filtres, détectées sur le champ {}." -#: awx/api/filters.py:297 +#: awx/api/filters.py:171 +#, python-brace-format +msgid "Invalid {field_name} id: {field_id}" +msgstr "Id {field_name} non valide : {field_id}" + +#: awx/api/filters.py:302 #, python-format msgid "cannot filter on kind %s" msgstr "impossible de filtrer sur le genre %s" -#: awx/api/filters.py:404 +#: awx/api/filters.py:409 #, python-format msgid "cannot order by field %s" msgstr "impossible de trier par champ %s" -#: awx/api/generics.py:515 awx/api/generics.py:577 +#: awx/api/generics.py:550 awx/api/generics.py:612 msgid "\"id\" field must be an integer." msgstr "Le champ \"id\" doit être un nombre entier." -#: awx/api/generics.py:574 +#: awx/api/generics.py:609 msgid "\"id\" is required to disassociate" msgstr "\"id\" est nécessaire pour dissocier" -#: awx/api/generics.py:625 +#: awx/api/generics.py:660 msgid "{} 'id' field is missing." msgstr "Le champ {} 'id' est manquant." @@ -182,7 +187,7 @@ msgstr "Tâche de workflow" msgid "Workflow Template" msgstr "Modèle de workflow" -#: awx/api/serializers.py:697 awx/api/serializers.py:755 awx/api/views.py:4314 +#: awx/api/serializers.py:701 awx/api/serializers.py:759 awx/api/views.py:4365 #, python-format msgid "" "Standard Output too large to display (%(text_size)d bytes), only download " @@ -192,38 +197,38 @@ msgstr "" "Le téléchargement est pris en charge seulement pour une taille supérieure à " "%(supported_size)d octets" -#: awx/api/serializers.py:770 +#: awx/api/serializers.py:774 msgid "Write-only field used to change the password." msgstr "Champ en écriture seule servant à modifier le mot de passe." -#: awx/api/serializers.py:772 +#: awx/api/serializers.py:776 msgid "Set if the account is managed by an external service" msgstr "À définir si le compte est géré par un service externe" -#: awx/api/serializers.py:796 +#: awx/api/serializers.py:800 msgid "Password required for new User." msgstr "Mot de passe requis pour le nouvel utilisateur." -#: awx/api/serializers.py:882 +#: awx/api/serializers.py:886 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "Impossible de redéfinir %s sur un utilisateur géré par LDAP." -#: awx/api/serializers.py:1046 +#: awx/api/serializers.py:1050 msgid "Organization is missing" msgstr "L'organisation est manquante" -#: awx/api/serializers.py:1050 +#: awx/api/serializers.py:1054 msgid "Update options must be set to false for manual projects." msgstr "" "La Mise à jour des options doit être définie à false pour les projets " "manuels." -#: awx/api/serializers.py:1056 +#: awx/api/serializers.py:1060 msgid "Array of playbooks available within this project." msgstr "Tableau des playbooks disponibles dans ce projet." -#: awx/api/serializers.py:1075 +#: awx/api/serializers.py:1079 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." @@ -231,67 +236,71 @@ msgstr "" "Tableau des fichiers d'inventaires et des répertoires disponibles dans ce " "projet : incomplet." -#: awx/api/serializers.py:1282 +#: awx/api/serializers.py:1201 +msgid "Smart inventories must specify host_filter" +msgstr "Les inventaires smart doivent spécifier le host_filter" + +#: awx/api/serializers.py:1303 #, python-format msgid "Invalid port specification: %s" msgstr "Spécification de port non valide : %s" -#: awx/api/serializers.py:1293 +#: awx/api/serializers.py:1314 msgid "Cannot create Host for Smart Inventory" msgstr "Impossible de créer un Hôte pour l' Inventaire Smart" -#: awx/api/serializers.py:1315 awx/api/serializers.py:3218 -#: awx/api/serializers.py:3303 awx/main/validators.py:198 +#: awx/api/serializers.py:1336 awx/api/serializers.py:3321 +#: awx/api/serializers.py:3406 awx/main/validators.py:198 msgid "Must be valid JSON or YAML." msgstr "Syntaxe JSON ou YAML valide exigée." -#: awx/api/serializers.py:1411 +#: awx/api/serializers.py:1432 msgid "Invalid group name." msgstr "Nom de groupe incorrect." -#: awx/api/serializers.py:1416 +#: awx/api/serializers.py:1437 msgid "Cannot create Group for Smart Inventory" msgstr "Impossible de créer de Groupe pour l' Inventaire Smart" -#: awx/api/serializers.py:1488 +#: awx/api/serializers.py:1509 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" "Le script doit commencer par une séquence hashbang : c.-à-d. ... " "#!/usr/bin/env python" -#: awx/api/serializers.py:1534 +#: awx/api/serializers.py:1555 msgid "`{}` is a prohibited environment variable" msgstr "`{}`est une variable d'environnement interdite" -#: awx/api/serializers.py:1545 +#: awx/api/serializers.py:1566 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "Si la valeur 'source' est 'custom', 'source_script' doit être défini." -#: awx/api/serializers.py:1551 +#: awx/api/serializers.py:1572 msgid "Must provide an inventory." msgstr "Vous devez fournir un inventaire." -#: awx/api/serializers.py:1555 +#: awx/api/serializers.py:1576 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" "Le 'source_script' n'appartient pas à la même organisation que l'inventaire." -#: awx/api/serializers.py:1557 +#: awx/api/serializers.py:1578 msgid "'source_script' doesn't exist." msgstr "'source_script' n'existe pas." -#: awx/api/serializers.py:1581 +#: awx/api/serializers.py:1602 msgid "Automatic group relationship, will be removed in 3.3" msgstr "Relation de groupe automatique, sera supprimée dans la version 3.3" -#: awx/api/serializers.py:1658 +#: awx/api/serializers.py:1679 msgid "Cannot use manual project for SCM-based inventory." msgstr "Impossible d'utiliser un projet manuel pour un inventaire SCM." -#: awx/api/serializers.py:1664 +#: awx/api/serializers.py:1685 msgid "" "Manual inventory sources are created automatically when a group is created " "in the v1 API." @@ -299,54 +308,62 @@ msgstr "" "Les sources d'inventaire manuel sont créées automatiquement lorsqu'un groupe" " est créé dans l'API v1." -#: awx/api/serializers.py:1669 +#: awx/api/serializers.py:1690 msgid "Setting not compatible with existing schedules." msgstr "Paramètre incompatible avec les planifications existantes." -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1695 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "Impossible de créer une Source d'inventaire pour l' Inventaire Smart" -#: awx/api/serializers.py:1688 +#: awx/api/serializers.py:1709 #, python-format msgid "Cannot set %s if not SCM type." msgstr "Impossible de définir %s si pas du type SCM ." -#: awx/api/serializers.py:1929 +#: awx/api/serializers.py:1950 msgid "Modifications not allowed for managed credential types" msgstr "" "Modifications non autorisées pour les types d'informations d'identification " "gérés" -#: awx/api/serializers.py:1934 +#: awx/api/serializers.py:1955 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "" "Modifications apportées aux entrées non autorisées pour les types " "d'informations d'identification en cours d'utilisation" -#: awx/api/serializers.py:1940 +#: awx/api/serializers.py:1961 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "Doit être 'cloud' ou 'net', pas %s" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1967 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "" "'ask_at_runtime' n'est pas pris en charge pour les informations " "d'identification personnalisées." -#: awx/api/serializers.py:2119 +#: awx/api/serializers.py:2140 #, python-format msgid "\"%s\" is not a valid choice" msgstr "\"%s\" n'est pas un choix valide." -#: awx/api/serializers.py:2138 +#: awx/api/serializers.py:2159 #, python-format msgid "'%s' is not a valid field for %s" msgstr "'%s' n'est pas un champ valide pour %s" -#: awx/api/serializers.py:2150 +#: awx/api/serializers.py:2180 +msgid "" +"You cannot change the credential type of the credential, as it may break the" +" functionality of the resources using it." +msgstr "" +"Vous ne pouvez pas modifier le type d'identifiants, car cela peut " +"compromettre la fonctionnalité de la ressource les utilisant." + +#: awx/api/serializers.py:2191 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." @@ -355,7 +372,7 @@ msgstr "" "propriétaire. Si vous le définissez, n'entrez ni équipe ni organisation. " "Seulement valable pour la création." -#: awx/api/serializers.py:2155 +#: awx/api/serializers.py:2196 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." @@ -364,7 +381,7 @@ msgstr "" "propriétaire. Si vous le définissez, n'entrez ni utilisateur ni " "organisation. Seulement valable pour la création." -#: awx/api/serializers.py:2160 +#: awx/api/serializers.py:2201 msgid "" "Inherit permissions from organization roles. If provided on creation, do not" " give either user or team." @@ -372,128 +389,132 @@ msgstr "" "Hériter des permissions à partir des rôles d'organisation. Si vous le " "définissez lors de la création, n'entrez ni utilisateur ni équipe." -#: awx/api/serializers.py:2176 +#: awx/api/serializers.py:2217 msgid "Missing 'user', 'team', or 'organization'." msgstr "Valeur 'utilisateur', 'équipe' ou 'organisation' manquante." -#: awx/api/serializers.py:2216 +#: awx/api/serializers.py:2257 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "" "L'organisation des informations d'identification doit être définie et mise " "en correspondance avant de l'attribuer à une équipe" -#: awx/api/serializers.py:2378 +#: awx/api/serializers.py:2424 msgid "You must provide a cloud credential." msgstr "Vous devez fournir une information d'identification cloud." -#: awx/api/serializers.py:2379 +#: awx/api/serializers.py:2425 msgid "You must provide a network credential." msgstr "Vous devez fournir des informations d'identification réseau." -#: awx/api/serializers.py:2395 +#: awx/api/serializers.py:2441 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: awx/api/serializers.py:2397 awx/api/serializers.py:2399 +#: awx/api/serializers.py:2443 awx/api/serializers.py:2445 msgid "Playbook not found for project." msgstr "Playbook introuvable pour le projet." -#: awx/api/serializers.py:2401 +#: awx/api/serializers.py:2447 msgid "Must select playbook for project." msgstr "Un playbook doit être sélectionné pour le project." -#: awx/api/serializers.py:2476 +#: awx/api/serializers.py:2522 msgid "Must either set a default value or ask to prompt on launch." msgstr "" "Une valeur par défaut doit être définie ou bien demander une invite au " "moment du lancement." -#: awx/api/serializers.py:2478 awx/main/models/jobs.py:325 +#: awx/api/serializers.py:2524 awx/main/models/jobs.py:326 msgid "Job types 'run' and 'check' must have assigned a project." msgstr "Un projet doit être assigné aux types de tâche 'run' et 'check'." -#: awx/api/serializers.py:2549 +#: awx/api/serializers.py:2611 msgid "Invalid job template." msgstr "Modèle de tâche non valide." -#: awx/api/serializers.py:2630 -msgid "Credential not found or deleted." -msgstr "Informations d'identification introuvables ou supprimées." +#: awx/api/serializers.py:2708 +msgid "Neither credential nor vault credential provided." +msgstr "Indentifiants et identifiants de l'archivage sécurisé non fournis" -#: awx/api/serializers.py:2632 +#: awx/api/serializers.py:2711 msgid "Job Template Project is missing or undefined." msgstr "Le projet de modèle de tâche est manquant ou non défini." -#: awx/api/serializers.py:2634 +#: awx/api/serializers.py:2713 msgid "Job Template Inventory is missing or undefined." msgstr "Le projet de modèle d'inventaire est manquant ou non défini." -#: awx/api/serializers.py:2921 +#: awx/api/serializers.py:2782 awx/main/tasks.py:2186 +msgid "{} are prohibited from use in ad hoc commands." +msgstr "{} ne sont pas autorisés à utiliser les commandes ad hoc." + +#: awx/api/serializers.py:3008 #, python-format msgid "%(job_type)s is not a valid job type. The choices are %(choices)s." msgstr "" "%(job_type)s n'est pas un type de tâche valide. Les choix sont %(choices)s." -#: awx/api/serializers.py:2926 +#: awx/api/serializers.py:3013 msgid "Workflow job template is missing during creation." msgstr "Le modèle de tâche Workflow est manquant lors de la création." -#: awx/api/serializers.py:2931 +#: awx/api/serializers.py:3018 #, python-format msgid "Cannot nest a %s inside a WorkflowJobTemplate" msgstr "Impossible d'imbriquer %s dans un modèle de tâche Workflow." -#: awx/api/serializers.py:3188 +#: awx/api/serializers.py:3291 #, python-format msgid "Job Template '%s' is missing or undefined." msgstr "Le modèle de tâche '%s' est manquant ou non défini." -#: awx/api/serializers.py:3191 +#: awx/api/serializers.py:3294 msgid "The inventory associated with this Job Template is being deleted." msgstr "" "L'inventaire associé à ce modèle de tâche est en cours de suppression." -#: awx/api/serializers.py:3232 awx/api/views.py:2991 +#: awx/api/serializers.py:3335 awx/api/views.py:3023 #, python-format msgid "Cannot assign multiple %s credentials." msgstr "Impossible d'attribuer plusieurs informations d'identification %s ." -#: awx/api/serializers.py:3234 awx/api/views.py:2994 +#: awx/api/serializers.py:3337 awx/api/views.py:3026 msgid "Extra credentials must be network or cloud." msgstr "" "Les informations d'identification supplémentaires doivent être des " "identifiants réseau ou cloud." -#: awx/api/serializers.py:3371 +#: awx/api/serializers.py:3474 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" "Champs obligatoires manquants pour la configuration des notifications : " "notification_type" -#: awx/api/serializers.py:3394 +#: awx/api/serializers.py:3497 msgid "No values specified for field '{}'" msgstr "Aucune valeur spécifiée pour le champ '{}'" -#: awx/api/serializers.py:3399 +#: awx/api/serializers.py:3502 msgid "Missing required fields for Notification Configuration: {}." msgstr "" "Champs obligatoires manquants pour la configuration des notifications : {}." -#: awx/api/serializers.py:3402 +#: awx/api/serializers.py:3505 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "Type de champ de configuration '{}' incorrect, {} attendu." -#: awx/api/serializers.py:3455 +#: awx/api/serializers.py:3558 msgid "Inventory Source must be a cloud resource." msgstr "La source d'inventaire doit être une ressource cloud." -#: awx/api/serializers.py:3457 +#: awx/api/serializers.py:3560 msgid "Manual Project cannot have a schedule set." msgstr "Le projet manuel ne peut pas avoir de calendrier défini." -#: awx/api/serializers.py:3460 +#: awx/api/serializers.py:3563 msgid "" "Inventory sources with `update_on_project_update` cannot be scheduled. " "Schedule its source project `{}` instead." @@ -501,72 +522,72 @@ msgstr "" "Impossible de planifier les sources d'inventaire avec " "`update_on_project_update`. Planifiez plutôt son projet source`{}`." -#: awx/api/serializers.py:3479 +#: awx/api/serializers.py:3582 msgid "Projects and inventory updates cannot accept extra variables." msgstr "" "Les projets et mises à jour d'inventaire ne peuvent pas accepter de " "variables supplémentaires." -#: awx/api/serializers.py:3501 +#: awx/api/serializers.py:3604 msgid "" "DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ" msgstr "" "DTSTART obligatoire dans rrule. La valeur doit correspondre à : " "DTSTART:YYYYMMDDTHHMMSSZ" -#: awx/api/serializers.py:3503 +#: awx/api/serializers.py:3606 msgid "Multiple DTSTART is not supported." msgstr "Une seule valeur DTSTART est prise en charge." -#: awx/api/serializers.py:3505 +#: awx/api/serializers.py:3608 msgid "RRULE require in rrule." msgstr "RRULE obligatoire dans rrule." -#: awx/api/serializers.py:3507 +#: awx/api/serializers.py:3610 msgid "Multiple RRULE is not supported." msgstr "Une seule valeur RRULE est prise en charge." -#: awx/api/serializers.py:3509 +#: awx/api/serializers.py:3612 msgid "INTERVAL required in rrule." msgstr "INTERVAL obligatoire dans rrule." -#: awx/api/serializers.py:3511 +#: awx/api/serializers.py:3614 msgid "TZID is not supported." msgstr "TZID n'est pas pris en charge." -#: awx/api/serializers.py:3513 +#: awx/api/serializers.py:3616 msgid "SECONDLY is not supported." msgstr "SECONDLY n'est pas pris en charge." -#: awx/api/serializers.py:3515 +#: awx/api/serializers.py:3618 msgid "Multiple BYMONTHDAYs not supported." msgstr "Une seule valeur BYMONTHDAY est prise en charge." -#: awx/api/serializers.py:3517 +#: awx/api/serializers.py:3620 msgid "Multiple BYMONTHs not supported." msgstr "Une seule valeur BYMONTH est prise en charge." -#: awx/api/serializers.py:3519 +#: awx/api/serializers.py:3622 msgid "BYDAY with numeric prefix not supported." msgstr "BYDAY avec un préfixe numérique non pris en charge." -#: awx/api/serializers.py:3521 +#: awx/api/serializers.py:3624 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY non pris en charge." -#: awx/api/serializers.py:3523 +#: awx/api/serializers.py:3626 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO non pris en charge." -#: awx/api/serializers.py:3527 +#: awx/api/serializers.py:3630 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 non pris en charge." -#: awx/api/serializers.py:3531 +#: awx/api/serializers.py:3634 msgid "rrule parsing failed validation." msgstr "L'analyse rrule n'a pas pu être validée." -#: awx/api/serializers.py:3631 +#: awx/api/serializers.py:3760 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" @@ -574,7 +595,7 @@ msgstr "" "Un récapitulatif des valeurs nouvelles et modifiées lorsqu'un objet est " "créé, mis à jour ou supprimé" -#: awx/api/serializers.py:3633 +#: awx/api/serializers.py:3762 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " @@ -584,7 +605,7 @@ msgstr "" "d'objet qui a été affecté. Pour associer et dissocier des événements, il " "s'agit du type d'objet associé à ou dissocié de object2." -#: awx/api/serializers.py:3636 +#: awx/api/serializers.py:3765 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated" @@ -594,166 +615,166 @@ msgstr "" "associer et dissocier des événements, il s'agit du type d'objet auquel " "object1 est associé." -#: awx/api/serializers.py:3639 +#: awx/api/serializers.py:3768 msgid "The action taken with respect to the given object(s)." msgstr "Action appliquée par rapport à l'objet ou aux objets donnés." -#: awx/api/serializers.py:3749 +#: awx/api/serializers.py:3885 msgid "Unable to login with provided credentials." msgstr "Connexion impossible avec les informations d'identification fournies." -#: awx/api/serializers.py:3751 +#: awx/api/serializers.py:3887 msgid "Must include \"username\" and \"password\"." msgstr "Elles doivent inclure le nom d'utilisateur et le mot de passe." -#: awx/api/views.py:106 +#: awx/api/views.py:108 msgid "Your license does not allow use of the activity stream." msgstr "Votre licence ne permet pas l'utilisation du flux d'activité." -#: awx/api/views.py:116 +#: awx/api/views.py:118 msgid "Your license does not permit use of system tracking." msgstr "Votre licence ne permet pas l'utilisation du suivi du système." -#: awx/api/views.py:126 +#: awx/api/views.py:128 msgid "Your license does not allow use of workflows." msgstr "Votre licence ne permet pas l'utilisation de workflows." -#: awx/api/views.py:140 +#: awx/api/views.py:142 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" "Impossible de supprimer les ressources de tâche lorsqu'une tâche de workflow" " associée est en cours d'exécution." -#: awx/api/views.py:144 +#: awx/api/views.py:146 msgid "Cannot delete running job resource." msgstr "Impossible de supprimer la ressource de la tâche en cours." -#: awx/api/views.py:153 awx/templates/rest_framework/api.html:28 +#: awx/api/views.py:155 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "API REST" -#: awx/api/views.py:162 awx/templates/rest_framework/api.html:4 +#: awx/api/views.py:164 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "API REST AWX" -#: awx/api/views.py:224 +#: awx/api/views.py:226 msgid "Version 1" msgstr "Version 1" -#: awx/api/views.py:228 +#: awx/api/views.py:230 msgid "Version 2" msgstr "Version 2" -#: awx/api/views.py:239 +#: awx/api/views.py:241 msgid "Ping" msgstr "Ping" -#: awx/api/views.py:270 awx/conf/apps.py:12 +#: awx/api/views.py:272 awx/conf/apps.py:12 msgid "Configuration" msgstr "Configuration" -#: awx/api/views.py:323 +#: awx/api/views.py:325 msgid "Invalid license data" msgstr "Données de licence non valides" -#: awx/api/views.py:325 +#: awx/api/views.py:327 msgid "Missing 'eula_accepted' property" msgstr "Propriété 'eula_accepted' manquante" -#: awx/api/views.py:329 +#: awx/api/views.py:331 msgid "'eula_accepted' value is invalid" msgstr "La valeur 'eula_accepted' n'est pas valide" -#: awx/api/views.py:332 +#: awx/api/views.py:334 msgid "'eula_accepted' must be True" msgstr "La valeur 'eula_accepted' doit être True" -#: awx/api/views.py:339 +#: awx/api/views.py:341 msgid "Invalid JSON" msgstr "Syntaxe JSON non valide" -#: awx/api/views.py:347 +#: awx/api/views.py:349 msgid "Invalid License" msgstr "Licence non valide" -#: awx/api/views.py:357 +#: awx/api/views.py:359 msgid "Invalid license" msgstr "Licence non valide" -#: awx/api/views.py:365 +#: awx/api/views.py:367 #, python-format msgid "Failed to remove license (%s)" msgstr "Suppression de la licence (%s) impossible" -#: awx/api/views.py:370 +#: awx/api/views.py:372 msgid "Dashboard" msgstr "Tableau de bord" -#: awx/api/views.py:469 +#: awx/api/views.py:471 msgid "Dashboard Jobs Graphs" msgstr "Graphiques de tâches du tableau de bord" -#: awx/api/views.py:505 +#: awx/api/views.py:507 #, python-format msgid "Unknown period \"%s\"" msgstr "Période \"%s\" inconnue" -#: awx/api/views.py:519 +#: awx/api/views.py:521 msgid "Instances" msgstr "Instances" -#: awx/api/views.py:527 +#: awx/api/views.py:529 msgid "Instance Detail" msgstr "Détail de l'instance" -#: awx/api/views.py:535 +#: awx/api/views.py:537 msgid "Instance Running Jobs" msgstr "Instance exécutant les tâches" -#: awx/api/views.py:550 +#: awx/api/views.py:552 msgid "Instance's Instance Groups" msgstr "Groupes d'instances de l'instance" -#: awx/api/views.py:560 +#: awx/api/views.py:562 msgid "Instance Groups" msgstr "Groupes d'instances" -#: awx/api/views.py:568 +#: awx/api/views.py:570 msgid "Instance Group Detail" msgstr "Détail du groupe d'instances" -#: awx/api/views.py:576 +#: awx/api/views.py:578 msgid "Instance Group Running Jobs" msgstr "Groupe d'instances exécutant les tâches" -#: awx/api/views.py:586 +#: awx/api/views.py:588 msgid "Instance Group's Instances" msgstr "Instances du groupe d'instances" -#: awx/api/views.py:596 +#: awx/api/views.py:598 msgid "Schedules" msgstr "Calendriers" -#: awx/api/views.py:615 +#: awx/api/views.py:617 msgid "Schedule Jobs List" msgstr "Listes des tâches de planification" -#: awx/api/views.py:841 +#: awx/api/views.py:843 msgid "Your license only permits a single organization to exist." msgstr "Votre licence permet uniquement l'existence d'une seule organisation." -#: awx/api/views.py:1077 awx/api/views.py:4615 +#: awx/api/views.py:1079 awx/api/views.py:4666 msgid "You cannot assign an Organization role as a child role for a Team." msgstr "" "Vous ne pouvez pas attribuer un rôle Organisation en tant que rôle enfant " "pour une équipe." -#: awx/api/views.py:1081 awx/api/views.py:4629 +#: awx/api/views.py:1083 awx/api/views.py:4680 msgid "You cannot grant system-level permissions to a team." msgstr "" "Vous ne pouvez pas accorder de permissions au niveau système à une équipe." -#: awx/api/views.py:1088 awx/api/views.py:4621 +#: awx/api/views.py:1090 awx/api/views.py:4672 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" @@ -762,36 +783,36 @@ msgstr "" "équipe lorsque le champ Organisation n'est pas défini ou qu'elle appartient " "à une organisation différente" -#: awx/api/views.py:1178 +#: awx/api/views.py:1180 msgid "Cannot delete project." msgstr "Suppression du projet impossible." -#: awx/api/views.py:1213 +#: awx/api/views.py:1215 msgid "Project Schedules" msgstr "Calendriers des projets" -#: awx/api/views.py:1225 +#: awx/api/views.py:1227 msgid "Project SCM Inventory Sources" msgstr "Sources d'inventaire SCM du projet" -#: awx/api/views.py:1352 +#: awx/api/views.py:1354 msgid "Project Update SCM Inventory Updates" msgstr "Mises à jour de l'inventaire SCM de la mise à jour du projet" -#: awx/api/views.py:1407 +#: awx/api/views.py:1409 msgid "Me" msgstr "Moi-même" -#: awx/api/views.py:1451 awx/api/views.py:4572 +#: awx/api/views.py:1453 awx/api/views.py:4623 msgid "You may not perform any action with your own admin_role." msgstr "Vous ne pouvez pas effectuer d'action avec votre propre admin_role." -#: awx/api/views.py:1457 awx/api/views.py:4576 +#: awx/api/views.py:1459 awx/api/views.py:4627 msgid "You may not change the membership of a users admin_role" msgstr "" "Vous ne pouvez pas modifier l'appartenance de l'admin_role d'un utilisateur" -#: awx/api/views.py:1462 awx/api/views.py:4581 +#: awx/api/views.py:1464 awx/api/views.py:4632 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" @@ -800,64 +821,65 @@ msgstr "" "utilisateur ne figurant pas dans l'organisation d'informations " "d'identification." -#: awx/api/views.py:1466 awx/api/views.py:4585 +#: awx/api/views.py:1468 awx/api/views.py:4636 msgid "You cannot grant private credential access to another user" msgstr "" "Vous ne pouvez pas accorder d'accès privé par informations d'identification " "à un autre utilisateur" -#: awx/api/views.py:1564 +#: awx/api/views.py:1566 #, python-format msgid "Cannot change %s." msgstr "Impossible de modifier %s." -#: awx/api/views.py:1570 +#: awx/api/views.py:1572 msgid "Cannot delete user." msgstr "Impossible de supprimer l'utilisateur." -#: awx/api/views.py:1599 +#: awx/api/views.py:1601 msgid "Deletion not allowed for managed credential types" msgstr "" "Suppression non autorisée pour les types d'informations d'identification " "gérés." -#: awx/api/views.py:1601 +#: awx/api/views.py:1603 msgid "Credential types that are in use cannot be deleted" msgstr "" "Les types d'informations d'identification utilisés ne peuvent pas être " "supprimés." -#: awx/api/views.py:1779 +#: awx/api/views.py:1781 msgid "Cannot delete inventory script." msgstr "Impossible de supprimer le script d'inventaire." -#: awx/api/views.py:1864 +#: awx/api/views.py:1866 +#, python-brace-format msgid "{0}" msgstr "{0}" -#: awx/api/views.py:2089 +#: awx/api/views.py:2101 msgid "Fact not found." msgstr "Fait introuvable." -#: awx/api/views.py:2113 +#: awx/api/views.py:2125 msgid "SSLError while trying to connect to {}" msgstr "ErreurSSL lors de la tentative de connexion au {} " -#: awx/api/views.py:2115 +#: awx/api/views.py:2127 msgid "Request to {} timed out." msgstr "Délai de requête {} expiré." -#: awx/api/views.py:2117 +#: awx/api/views.py:2129 msgid "Unkown exception {} while trying to GET {}" msgstr "Exception inconnue {} lors de la tentative GET {}" -#: awx/api/views.py:2120 +#: awx/api/views.py:2132 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "Accès non autorisé. Veuillez vérifier vos identifiants pour Insights." -#: awx/api/views.py:2122 +#: awx/api/views.py:2134 msgid "" "Failed to gather reports and maintenance plans from Insights API at URL {}. " "Server responded with {} status code and message {}" @@ -866,202 +888,216 @@ msgstr "" "sur l'URL {}. Le serveur a répondu avec un code de statut {} et un message " "{} " -#: awx/api/views.py:2128 +#: awx/api/views.py:2140 msgid "Expected JSON response from Insights but instead got {}" msgstr "" "Réponse JSON attendue de la part d'Insights, mais {} a été reçu à la place" -#: awx/api/views.py:2135 +#: awx/api/views.py:2147 msgid "This host is not recognized as an Insights host." msgstr "Cet hôte n'est pas reconnu comme hôte Insights." -#: awx/api/views.py:2140 +#: awx/api/views.py:2152 msgid "The Insights Credential for \"{}\" was not found." msgstr "Informations d'identification Insights pour \"{}\" introuvables." -#: awx/api/views.py:2209 +#: awx/api/views.py:2221 msgid "Cyclical Group association." msgstr "Association de groupe cyclique." -#: awx/api/views.py:2482 +#: awx/api/views.py:2499 msgid "Inventory Source List" msgstr "Liste des sources d'inventaire" -#: awx/api/views.py:2495 +#: awx/api/views.py:2512 msgid "Inventory Sources Update" msgstr "Mise à jour des sources d'inventaire" -#: awx/api/views.py:2525 +#: awx/api/views.py:2542 msgid "Could not start because `can_update` returned False" msgstr "Démarrage impossible car `can_update` a renvoyé False" -#: awx/api/views.py:2533 +#: awx/api/views.py:2550 msgid "No inventory sources to update." msgstr "Aucune source d'inventaire à actualiser." -#: awx/api/views.py:2565 +#: awx/api/views.py:2582 msgid "Cannot delete inventory source." msgstr "Impossible de supprimer la source d'inventaire." -#: awx/api/views.py:2573 +#: awx/api/views.py:2590 msgid "Inventory Source Schedules" msgstr "Calendriers des sources d'inventaire" -#: awx/api/views.py:2603 +#: awx/api/views.py:2620 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" "Les modèles de notification ne peuvent être attribués que lorsque la source " "est l'une des {}." -#: awx/api/views.py:2826 +#: awx/api/views.py:2851 msgid "Job Template Schedules" msgstr "Calendriers des modèles de tâche" -#: awx/api/views.py:2846 awx/api/views.py:2862 +#: awx/api/views.py:2871 awx/api/views.py:2882 msgid "Your license does not allow adding surveys." msgstr "Votre licence ne permet pas l'ajout de questionnaires." -#: awx/api/views.py:2869 +#: awx/api/views.py:2889 msgid "'name' missing from survey spec." msgstr "'name' manquant dans la spécification du questionnaire." -#: awx/api/views.py:2871 +#: awx/api/views.py:2891 msgid "'description' missing from survey spec." msgstr "'description' manquante dans la spécification du questionnaire." -#: awx/api/views.py:2873 +#: awx/api/views.py:2893 msgid "'spec' missing from survey spec." msgstr "'spec' manquante dans la spécification du questionnaire." -#: awx/api/views.py:2875 +#: awx/api/views.py:2895 msgid "'spec' must be a list of items." msgstr "'spec' doit être une liste d'éléments" -#: awx/api/views.py:2877 +#: awx/api/views.py:2897 msgid "'spec' doesn't contain any items." msgstr "'spec' ne contient aucun élément." -#: awx/api/views.py:2883 +#: awx/api/views.py:2903 #, python-format msgid "Survey question %s is not a json object." msgstr "La question %s n'est pas un objet json." -#: awx/api/views.py:2885 +#: awx/api/views.py:2905 #, python-format msgid "'type' missing from survey question %s." msgstr "'type' est manquant dans la question %s." -#: awx/api/views.py:2887 +#: awx/api/views.py:2907 #, python-format msgid "'question_name' missing from survey question %s." msgstr "'question_name' est manquant dans la question %s." -#: awx/api/views.py:2889 +#: awx/api/views.py:2909 #, python-format msgid "'variable' missing from survey question %s." msgstr "'variable' est manquant dans la question %s." -#: awx/api/views.py:2891 +#: awx/api/views.py:2911 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "'variable' '%(item)s' en double dans la question %(survey)s." -#: awx/api/views.py:2896 +#: awx/api/views.py:2916 #, python-format msgid "'required' missing from survey question %s." msgstr "'required' est manquant dans la question %s." -#: awx/api/views.py:2901 +#: awx/api/views.py:2921 +#, python-brace-format msgid "" -"$encrypted$ is reserved keyword and may not be used as a default for " -"password {}." -msgstr "" -"$encrypted$ est un mot clé réservé et ne peut pas être utilisé comme valeur " -"par défaut pour le mot de passe {}. " +"Value {question_default} for '{variable_name}' expected to be a string." +msgstr "Valeur {question_default} de '{variable_name}' de chaîne attendue. " -#: awx/api/views.py:3017 +#: awx/api/views.py:2928 +#, python-brace-format +msgid "" +"$encrypted$ is reserved keyword for password questions and may not be used " +"as a default for '{variable_name}' in survey question {question_position}." +msgstr "" +"$encrypted$ est un mot clé réservé pour les questions de mots de passe et ne" +" peuvent pas être utilisées par défaut pour '{variable_name}' dans les " +"questions d'enquêtes {question_position}." + +#: awx/api/views.py:3049 msgid "Maximum number of labels for {} reached." msgstr "Nombre maximum d'étiquettes {} atteint." -#: awx/api/views.py:3136 +#: awx/api/views.py:3170 msgid "No matching host could be found!" msgstr "Aucun hôte correspondant n'a été trouvé." -#: awx/api/views.py:3139 +#: awx/api/views.py:3173 msgid "Multiple hosts matched the request!" msgstr "Plusieurs hôtes correspondent à la requête." -#: awx/api/views.py:3144 +#: awx/api/views.py:3178 msgid "Cannot start automatically, user input required!" msgstr "" "Impossible de démarrer automatiquement, saisie de l'utilisateur obligatoire." -#: awx/api/views.py:3151 +#: awx/api/views.py:3185 msgid "Host callback job already pending." msgstr "La tâche de rappel de l'hôte est déjà en attente." -#: awx/api/views.py:3164 +#: awx/api/views.py:3199 msgid "Error starting job!" msgstr "Erreur lors du démarrage de la tâche." -#: awx/api/views.py:3271 +#: awx/api/views.py:3306 +#, python-brace-format msgid "Cannot associate {0} when {1} have been associated." msgstr " {0} ne peut pas être associé lorsque {1} est associé." -#: awx/api/views.py:3296 +#: awx/api/views.py:3331 msgid "Multiple parent relationship not allowed." msgstr "Relation de parents multiples non autorisée." -#: awx/api/views.py:3301 +#: awx/api/views.py:3336 msgid "Cycle detected." msgstr "Cycle détecté." -#: awx/api/views.py:3505 +#: awx/api/views.py:3540 msgid "Workflow Job Template Schedules" msgstr "Calendriers des modèles de tâche Workflow" -#: awx/api/views.py:3650 awx/api/views.py:4217 +#: awx/api/views.py:3685 awx/api/views.py:4268 msgid "Superuser privileges needed." msgstr "Privilèges de superutilisateur requis." -#: awx/api/views.py:3682 +#: awx/api/views.py:3717 msgid "System Job Template Schedules" msgstr "Calendriers des modèles de tâche Système" -#: awx/api/views.py:3891 +#: awx/api/views.py:3780 +msgid "POST not allowed for Job launching in version 2 of the api" +msgstr "" +"POST non autorisé pour le lancement de tâches dans la version 2 de l'api" + +#: awx/api/views.py:3942 msgid "Job Host Summaries List" msgstr "Liste récapitulative des hôtes de la tâche" -#: awx/api/views.py:3938 +#: awx/api/views.py:3989 msgid "Job Event Children List" msgstr "Liste des enfants d'événement de la tâche" -#: awx/api/views.py:3947 +#: awx/api/views.py:3998 msgid "Job Event Hosts List" msgstr "Liste des hôtes d'événement de la tâche" -#: awx/api/views.py:3957 +#: awx/api/views.py:4008 msgid "Job Events List" msgstr "Liste des événements de la tâche" -#: awx/api/views.py:4171 +#: awx/api/views.py:4222 msgid "Ad Hoc Command Events List" msgstr "Liste d'événements de la commande ad hoc" -#: awx/api/views.py:4386 +#: awx/api/views.py:4437 msgid "Error generating stdout download file: {}" msgstr "Erreur lors de la génération du fichier de téléchargement stdout : {}" -#: awx/api/views.py:4399 +#: awx/api/views.py:4450 #, python-format msgid "Error generating stdout download file: %s" msgstr "Erreur lors de la génération du fichier de téléchargement stdout : %s" -#: awx/api/views.py:4444 +#: awx/api/views.py:4495 msgid "Delete not allowed while there are pending notifications" msgstr "Suppression non autorisée tant que des notifications sont en attente" -#: awx/api/views.py:4451 +#: awx/api/views.py:4502 msgid "Notification Template Test" msgstr "Test de modèle de notification" @@ -1218,15 +1254,16 @@ msgstr "Exemple de paramètre qui peut être différent pour chaque utilisateur. msgid "User" msgstr "Utilisateur" -#: awx/conf/fields.py:62 +#: awx/conf/fields.py:63 msgid "Enter a valid URL" msgstr "Entez une URL valide" -#: awx/conf/fields.py:94 +#: awx/conf/fields.py:95 +#, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input}\" n'est pas une chaîne valide." -#: awx/conf/license.py:19 +#: awx/conf/license.py:22 msgid "Your Tower license does not allow that." msgstr "Votre licence Tower ne vous y autorise pas." @@ -1246,7 +1283,12 @@ msgstr "" msgid "Skip commenting out settings in files." msgstr "Ignorer la saisie de commentaires de paramètres dans les fichiers." -#: awx/conf/management/commands/migrate_to_database_settings.py:61 +#: awx/conf/management/commands/migrate_to_database_settings.py:62 +msgid "Skip migrating and only comment out settings in files." +msgstr "" +"Ignorer la migration et ne dé-commenter que les configuration des fichiers." + +#: awx/conf/management/commands/migrate_to_database_settings.py:68 msgid "Backup existing settings files with this suffix." msgstr "Sauvegardez les fichiers de paramètres existants avec ce suffixe." @@ -1270,7 +1312,7 @@ msgstr "Modifié" msgid "User-Defaults" msgstr "Paramètres utilisateur par défaut" -#: awx/conf/registry.py:151 +#: awx/conf/registry.py:154 msgid "This value has been set manually in a settings file." msgstr "" "Cette valeur a été définie manuellement dans un fichier de configuration." @@ -1343,94 +1385,92 @@ msgstr "Détails du paramètre" msgid "Logging Connectivity Test" msgstr "Journalisation du test de connectivité" -#: awx/main/access.py:224 +#: awx/main/access.py:44 +msgid "Resource is being used by running jobs." +msgstr "Ressource utilisée par les tâches en cours." + +#: awx/main/access.py:237 #, python-format msgid "Bad data found in related field %s." msgstr "Données incorrectes trouvées dans le champ %s associé." -#: awx/main/access.py:268 +#: awx/main/access.py:281 msgid "License is missing." msgstr "La licence est manquante." -#: awx/main/access.py:270 +#: awx/main/access.py:283 msgid "License has expired." msgstr "La licence est arrivée à expiration." -#: awx/main/access.py:278 +#: awx/main/access.py:291 #, python-format msgid "License count of %s instances has been reached." msgstr "Le nombre de licences d'instances %s a été atteint." -#: awx/main/access.py:280 +#: awx/main/access.py:293 #, python-format msgid "License count of %s instances has been exceeded." msgstr "Le nombre de licences d'instances %s a été dépassé." -#: awx/main/access.py:282 +#: awx/main/access.py:295 msgid "Host count exceeds available instances." msgstr "Le nombre d'hôtes dépasse celui des instances disponibles." -#: awx/main/access.py:286 +#: awx/main/access.py:299 #, python-format msgid "Feature %s is not enabled in the active license." msgstr "La fonctionnalité %s n'est pas activée dans la licence active." -#: awx/main/access.py:288 +#: awx/main/access.py:301 msgid "Features not found in active license." msgstr "Fonctionnalités introuvables dans la licence active." -#: awx/main/access.py:534 awx/main/access.py:619 awx/main/access.py:748 -#: awx/main/access.py:801 awx/main/access.py:1063 awx/main/access.py:1263 -#: awx/main/access.py:1725 -msgid "Resource is being used by running jobs" -msgstr "La ressource est utilisée par des tâches en cours d'exécution" - -#: awx/main/access.py:675 +#: awx/main/access.py:707 msgid "Unable to change inventory on a host." msgstr "Impossible de modifier l'inventaire sur un hôte." -#: awx/main/access.py:692 awx/main/access.py:737 +#: awx/main/access.py:724 awx/main/access.py:769 msgid "Cannot associate two items from different inventories." msgstr "Impossible d'associer deux éléments d'inventaires différents." -#: awx/main/access.py:725 +#: awx/main/access.py:757 msgid "Unable to change inventory on a group." msgstr "Impossible de modifier l'inventaire sur un groupe." -#: awx/main/access.py:983 +#: awx/main/access.py:1017 msgid "Unable to change organization on a team." msgstr "Impossible de modifier l'organisation d'une équipe." -#: awx/main/access.py:996 +#: awx/main/access.py:1030 msgid "The {} role cannot be assigned to a team" msgstr "Le rôle {} ne peut pas être attribué à une équipe" -#: awx/main/access.py:998 +#: awx/main/access.py:1032 msgid "The admin_role for a User cannot be assigned to a team" msgstr "L'admin_role d'un utilisateur ne peut pas être attribué à une équipe" -#: awx/main/access.py:1443 +#: awx/main/access.py:1479 msgid "Job has been orphaned from its job template." msgstr "La tâche est orpheline de son modèle de tâche." -#: awx/main/access.py:1445 +#: awx/main/access.py:1481 msgid "You do not have execute permission to related job template." msgstr "" "Vous n'avez pas de permission d'exécution pour le modèle de tâche lié." -#: awx/main/access.py:1448 +#: awx/main/access.py:1484 msgid "Job was launched with prompted fields." msgstr "La tâche a été lancée avec les champs d'invite." -#: awx/main/access.py:1450 +#: awx/main/access.py:1486 msgid " Organization level permissions required." msgstr "Permissions au niveau de l'organisation requises." -#: awx/main/access.py:1452 +#: awx/main/access.py:1488 msgid " You do not have permission to related resources." msgstr "Vous n'avez pas de permission vers les ressources liées." -#: awx/main/access.py:1798 +#: awx/main/access.py:1833 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." @@ -1974,87 +2014,87 @@ msgstr "" msgid "\n" msgstr "\n" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Sudo" msgstr "Sudo" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Su" msgstr "Su" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pbrun" msgstr "Pbrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pfexec" msgstr "Pfexec" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "DZDO" msgstr "DZDO" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pmrun" msgstr "Pmrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Runas" msgstr "Runas" -#: awx/main/fields.py:56 +#: awx/main/fields.py:57 #, python-format msgid "'%s' is not one of ['%s']" msgstr "'%s' ne fait pas partie des ['%s']" -#: awx/main/fields.py:531 +#: awx/main/fields.py:533 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "ne peut être défini à moins que \"%s\" soit défini" -#: awx/main/fields.py:547 +#: awx/main/fields.py:549 #, python-format msgid "required for %s" msgstr "requis pour %s" -#: awx/main/fields.py:571 +#: awx/main/fields.py:573 msgid "must be set when SSH key is encrypted." msgstr "doit être défini lorsque la clé SSH est chiffrée." -#: awx/main/fields.py:577 +#: awx/main/fields.py:579 msgid "should not be set when SSH key is not encrypted." msgstr "ne doit pas être défini lorsque la clé SSH n'est pas chiffrée." -#: awx/main/fields.py:635 +#: awx/main/fields.py:637 msgid "'dependencies' is not supported for custom credentials." msgstr "" "les dépendances ne sont pas prises en charge pour les identifiants " "personnalisés." -#: awx/main/fields.py:649 +#: awx/main/fields.py:651 msgid "\"tower\" is a reserved field name" msgstr "\"tower\" est un nom de champ réservé" -#: awx/main/fields.py:656 +#: awx/main/fields.py:658 #, python-format msgid "field IDs must be unique (%s)" msgstr "Les ID de champ doivent être uniques (%s)" -#: awx/main/fields.py:669 +#: awx/main/fields.py:671 #, python-format msgid "%s not allowed for %s type (%s)" msgstr "%s non autorisé pour le type %s (%s)" -#: awx/main/fields.py:753 +#: awx/main/fields.py:755 #, python-format msgid "%s uses an undefined field (%s)" msgstr "%s utilise un champ non défini (%s)" -#: awx/main/middleware.py:121 +#: awx/main/middleware.py:157 msgid "Formats of all available named urls" msgstr "Formats de toutes les URL nommées disponibles" -#: awx/main/middleware.py:122 +#: awx/main/middleware.py:158 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." @@ -2062,15 +2102,15 @@ msgstr "" "Liste en lecture seule de paires clé/valeur qui affiche le format standard " "de toutes les URL nommées disponibles." -#: awx/main/middleware.py:124 awx/main/middleware.py:134 +#: awx/main/middleware.py:160 awx/main/middleware.py:170 msgid "Named URL" msgstr "URL nommée" -#: awx/main/middleware.py:131 +#: awx/main/middleware.py:167 msgid "List of all named url graph nodes." msgstr "Liste de tous les noeuds de graphique des URL nommées." -#: awx/main/middleware.py:132 +#: awx/main/middleware.py:168 msgid "" "Read-only list of key-value pairs that exposes named URL graph topology. Use" " this list to programmatically generate named URLs for resources" @@ -2079,51 +2119,51 @@ msgstr "" "graphiques des URL nommées. Utilisez cette liste pour générer via un " "programme des URL nommées pour les ressources" -#: awx/main/migrations/_reencrypt.py:23 awx/main/models/notifications.py:33 +#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:33 msgid "Email" msgstr "Email" -#: awx/main/migrations/_reencrypt.py:24 awx/main/models/notifications.py:34 +#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:34 msgid "Slack" msgstr "Slack" -#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:35 +#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:35 msgid "Twilio" msgstr "Twilio" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:36 +#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:36 msgid "Pagerduty" msgstr "Pagerduty" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:37 +#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:37 msgid "HipChat" msgstr "HipChat" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:38 +#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:38 msgid "Webhook" msgstr "Webhook" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:39 +#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:39 msgid "IRC" msgstr "IRC" -#: awx/main/models/activity_stream.py:24 +#: awx/main/models/activity_stream.py:25 msgid "Entity Created" msgstr "Entité créée" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:26 msgid "Entity Updated" msgstr "Entité mise à jour" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:27 msgid "Entity Deleted" msgstr "Entité supprimée" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:28 msgid "Entity Associated with another Entity" msgstr "Entité associée à une autre entité" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:29 msgid "Entity was Disassociated with another Entity" msgstr "Entité dissociée d'une autre entité" @@ -2149,43 +2189,43 @@ msgstr "Module non pris en charge pour les commandes ad hoc." msgid "No argument passed to %s module." msgstr "Aucun argument transmis au module %s." -#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:904 +#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:911 msgid "Host Failed" msgstr "Échec de l'hôte" -#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:905 +#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:912 msgid "Host OK" msgstr "Hôte OK" -#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:908 +#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:915 msgid "Host Unreachable" msgstr "Hôte inaccessible" -#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:907 +#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:914 msgid "Host Skipped" msgstr "Hôte ignoré" -#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:935 +#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:942 msgid "Debug" msgstr "Déboguer" -#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:936 +#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:943 msgid "Verbose" msgstr "Verbeux" -#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:937 +#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:944 msgid "Deprecated" msgstr "Obsolète" -#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:938 +#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:945 msgid "Warning" msgstr "Avertissement" -#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:939 +#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:946 msgid "System Warning" msgstr "Avertissement système" -#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:940 +#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:947 #: awx/main/models/unified_jobs.py:64 msgid "Error" msgstr "Erreur" @@ -2204,27 +2244,27 @@ msgstr "Vérifier" msgid "Scan" msgstr "Scanner" -#: awx/main/models/credential.py:82 +#: awx/main/models/credential.py:86 msgid "Host" msgstr "Hôte" -#: awx/main/models/credential.py:83 +#: awx/main/models/credential.py:87 msgid "The hostname or IP address to use." msgstr "Nom d'hôte ou adresse IP à utiliser." -#: awx/main/models/credential.py:89 +#: awx/main/models/credential.py:93 msgid "Username" msgstr "Nom d'utilisateur" -#: awx/main/models/credential.py:90 +#: awx/main/models/credential.py:94 msgid "Username for this credential." msgstr "Nom d'utilisateur pour ces informations d'identification." -#: awx/main/models/credential.py:96 +#: awx/main/models/credential.py:100 msgid "Password" msgstr "Mot de passe" -#: awx/main/models/credential.py:97 +#: awx/main/models/credential.py:101 msgid "" "Password for this credential (or \"ASK\" to prompt the user for machine " "credentials)." @@ -2232,43 +2272,43 @@ msgstr "" "Mot de passe pour ces informations d'identification (ou \"ASK\" pour " "demander à l'utilisateur les informations d'identification de la machine)." -#: awx/main/models/credential.py:104 +#: awx/main/models/credential.py:108 msgid "Security Token" msgstr "Token de sécurité" -#: awx/main/models/credential.py:105 +#: awx/main/models/credential.py:109 msgid "Security Token for this credential" msgstr "Token de sécurité pour ces informations d'identification" -#: awx/main/models/credential.py:111 +#: awx/main/models/credential.py:115 msgid "Project" msgstr "Projet" -#: awx/main/models/credential.py:112 +#: awx/main/models/credential.py:116 msgid "The identifier for the project." msgstr "Identifiant du projet." -#: awx/main/models/credential.py:118 +#: awx/main/models/credential.py:122 msgid "Domain" msgstr "Domaine" -#: awx/main/models/credential.py:119 +#: awx/main/models/credential.py:123 msgid "The identifier for the domain." msgstr "Identifiant du domaine." -#: awx/main/models/credential.py:124 +#: awx/main/models/credential.py:128 msgid "SSH private key" msgstr "Clé privée SSH" -#: awx/main/models/credential.py:125 +#: awx/main/models/credential.py:129 msgid "RSA or DSA private key to be used instead of password." msgstr "Clé privée RSA ou DSA à utiliser au lieu du mot de passe." -#: awx/main/models/credential.py:131 +#: awx/main/models/credential.py:135 msgid "SSH key unlock" msgstr "Déverrouillage de la clé SSH" -#: awx/main/models/credential.py:132 +#: awx/main/models/credential.py:136 msgid "" "Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " "user for machine credentials)." @@ -2277,52 +2317,52 @@ msgstr "" "chiffrée (ou \"ASK\" pour demander à l'utilisateur les informations " "d'identification de la machine)." -#: awx/main/models/credential.py:139 +#: awx/main/models/credential.py:143 msgid "None" msgstr "Aucun" -#: awx/main/models/credential.py:140 +#: awx/main/models/credential.py:144 msgid "Privilege escalation method." msgstr "Méthode d'élévation des privilèges." -#: awx/main/models/credential.py:146 +#: awx/main/models/credential.py:150 msgid "Privilege escalation username." msgstr "Nom d'utilisateur pour l'élévation des privilèges" -#: awx/main/models/credential.py:152 +#: awx/main/models/credential.py:156 msgid "Password for privilege escalation method." msgstr "Mot de passe pour la méthode d'élévation des privilèges." -#: awx/main/models/credential.py:158 +#: awx/main/models/credential.py:162 msgid "Vault password (or \"ASK\" to prompt the user)." msgstr "Mot de passe Vault (ou \"ASK\" pour le demander à l'utilisateur)." -#: awx/main/models/credential.py:162 +#: awx/main/models/credential.py:166 msgid "Whether to use the authorize mechanism." msgstr "Indique s'il faut ou non utiliser le mécanisme d'autorisation." -#: awx/main/models/credential.py:168 +#: awx/main/models/credential.py:172 msgid "Password used by the authorize mechanism." msgstr "Mot de passe utilisé par le mécanisme d'autorisation." -#: awx/main/models/credential.py:174 +#: awx/main/models/credential.py:178 msgid "Client Id or Application Id for the credential" msgstr "" "ID du client ou de l'application pour les informations d'identification" -#: awx/main/models/credential.py:180 +#: awx/main/models/credential.py:184 msgid "Secret Token for this credential" msgstr "Token secret pour ces informations d'identification" -#: awx/main/models/credential.py:186 +#: awx/main/models/credential.py:190 msgid "Subscription identifier for this credential" msgstr "ID d'abonnement pour ces informations d'identification" -#: awx/main/models/credential.py:192 +#: awx/main/models/credential.py:196 msgid "Tenant identifier for this credential" msgstr "ID de tenant pour ces informations d'identification" -#: awx/main/models/credential.py:216 +#: awx/main/models/credential.py:220 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." @@ -2330,7 +2370,7 @@ msgstr "" "Spécifiez le type d'information d'identification à créer. Consultez la " "documentation d’Ansible Tower pour en savoir plus sur chaque type." -#: awx/main/models/credential.py:230 awx/main/models/credential.py:416 +#: awx/main/models/credential.py:234 awx/main/models/credential.py:420 msgid "" "Enter inputs using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2340,31 +2380,31 @@ msgstr "" "pour basculer entre les deux. Consultez la documentation d’Ansible Tower " "pour avoir un exemple de syntaxe." -#: awx/main/models/credential.py:397 +#: awx/main/models/credential.py:401 msgid "Machine" msgstr "Machine" -#: awx/main/models/credential.py:398 +#: awx/main/models/credential.py:402 msgid "Vault" msgstr "Coffre-fort" -#: awx/main/models/credential.py:399 +#: awx/main/models/credential.py:403 msgid "Network" msgstr "Réseau" -#: awx/main/models/credential.py:400 +#: awx/main/models/credential.py:404 msgid "Source Control" msgstr "Contrôle de la source" -#: awx/main/models/credential.py:401 +#: awx/main/models/credential.py:405 msgid "Cloud" msgstr "Cloud" -#: awx/main/models/credential.py:402 +#: awx/main/models/credential.py:406 msgid "Insights" msgstr "Insights" -#: awx/main/models/credential.py:423 +#: awx/main/models/credential.py:427 msgid "" "Enter injectors using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2374,6 +2414,11 @@ msgstr "" " pour basculer entre les deux. Consultez la documentation Ansible Tower pour" " avoir un exemple de syntaxe." +#: awx/main/models/credential.py:478 +#, python-format +msgid "adding %s credential type" +msgstr "ajout type d'identifiants %s" + #: awx/main/models/fact.py:25 msgid "Host for the facts that the fact scan captured." msgstr "Hôte pour les faits que le scan de faits a capturés." @@ -2392,11 +2437,11 @@ msgstr "" "Structure JSON arbitraire des faits de module capturés au moment de " "l'horodatage pour un seul hôte." -#: awx/main/models/ha.py:76 +#: awx/main/models/ha.py:78 msgid "Instances that are members of this InstanceGroup" msgstr "Instances membres de ce GroupeInstances." -#: awx/main/models/ha.py:81 +#: awx/main/models/ha.py:83 msgid "Instance Group to remotely control this group." msgstr "Groupe d'instances pour contrôler ce groupe à distance." @@ -2596,38 +2641,42 @@ msgid "Google Compute Engine" msgstr "Google Compute Engine" #: awx/main/models/inventory.py:870 -msgid "Microsoft Azure Classic (deprecated)" -msgstr "Microsoft Azure Classic (obsolète)" - -#: awx/main/models/inventory.py:871 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/inventory.py:872 +#: awx/main/models/inventory.py:871 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/inventory.py:873 +#: awx/main/models/inventory.py:872 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/inventory.py:874 +#: awx/main/models/inventory.py:873 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/inventory.py:875 +#: awx/main/models/inventory.py:874 msgid "OpenStack" msgstr "OpenStack" +#: awx/main/models/inventory.py:875 +msgid "oVirt4" +msgstr "oVirt4" + #: awx/main/models/inventory.py:876 +msgid "Ansible Tower" +msgstr "Ansible Tower" + +#: awx/main/models/inventory.py:877 msgid "Custom Script" msgstr "Script personnalisé" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:994 msgid "Inventory source variables in YAML or JSON format." msgstr "Variables de source d'inventaire au format JSON ou YAML." -#: awx/main/models/inventory.py:1012 +#: awx/main/models/inventory.py:1013 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." @@ -2635,75 +2684,75 @@ msgstr "" "Liste d'expressions de filtre séparées par des virgules (EC2 uniquement). " "Les hôtes sont importés lorsque l'UN des filtres correspondent." -#: awx/main/models/inventory.py:1018 +#: awx/main/models/inventory.py:1019 msgid "Limit groups automatically created from inventory source (EC2 only)." msgstr "" "Limiter automatiquement les groupes créés à partir de la source d'inventaire" " (EC2 uniquement)." -#: awx/main/models/inventory.py:1022 +#: awx/main/models/inventory.py:1023 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" "Écraser les groupes locaux et les hôtes de la source d'inventaire distante." -#: awx/main/models/inventory.py:1026 +#: awx/main/models/inventory.py:1027 msgid "Overwrite local variables from remote inventory source." msgstr "Écraser les variables locales de la source d'inventaire distante." -#: awx/main/models/inventory.py:1031 awx/main/models/jobs.py:159 +#: awx/main/models/inventory.py:1032 awx/main/models/jobs.py:160 #: awx/main/models/projects.py:117 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "Délai écoulé (en secondes) avant que la tâche ne soit annulée." -#: awx/main/models/inventory.py:1064 +#: awx/main/models/inventory.py:1065 msgid "Image ID" msgstr "ID d'image" -#: awx/main/models/inventory.py:1065 +#: awx/main/models/inventory.py:1066 msgid "Availability Zone" msgstr "Zone de disponibilité" -#: awx/main/models/inventory.py:1066 +#: awx/main/models/inventory.py:1067 msgid "Account" msgstr "Compte" -#: awx/main/models/inventory.py:1067 +#: awx/main/models/inventory.py:1068 msgid "Instance ID" msgstr "ID d'instance" -#: awx/main/models/inventory.py:1068 +#: awx/main/models/inventory.py:1069 msgid "Instance State" msgstr "État de l'instance" -#: awx/main/models/inventory.py:1069 +#: awx/main/models/inventory.py:1070 msgid "Instance Type" msgstr "Type d'instance" -#: awx/main/models/inventory.py:1070 +#: awx/main/models/inventory.py:1071 msgid "Key Name" msgstr "Nom de la clé" -#: awx/main/models/inventory.py:1071 +#: awx/main/models/inventory.py:1072 msgid "Region" msgstr "Région" -#: awx/main/models/inventory.py:1072 +#: awx/main/models/inventory.py:1073 msgid "Security Group" msgstr "Groupe de sécurité" -#: awx/main/models/inventory.py:1073 +#: awx/main/models/inventory.py:1074 msgid "Tags" msgstr "Balises" -#: awx/main/models/inventory.py:1074 +#: awx/main/models/inventory.py:1075 msgid "Tag None" msgstr "Ne rien baliser" -#: awx/main/models/inventory.py:1075 +#: awx/main/models/inventory.py:1076 msgid "VPC ID" msgstr "ID VPC" -#: awx/main/models/inventory.py:1138 +#: awx/main/models/inventory.py:1145 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " @@ -2712,31 +2761,40 @@ msgstr "" "Les sources d'inventaire cloud (telles que %s) requièrent des informations " "d'identification pour le service cloud correspondant." -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:1152 msgid "Credential is required for a cloud source." msgstr "" "Les informations d'identification sont requises pour une source cloud." -#: awx/main/models/inventory.py:1167 +#: awx/main/models/inventory.py:1155 +msgid "" +"Credentials of type machine, source control, insights and vault are " +"disallowed for custom inventory sources." +msgstr "" +"Les identifiants de type machine, contrôle de la source, insights ou " +"archivage sécurisé ne sont pas autorisés par les sources d'inventaire " +"personnalisées." + +#: awx/main/models/inventory.py:1179 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "Région %(source)s non valide : %(region)s" -#: awx/main/models/inventory.py:1191 +#: awx/main/models/inventory.py:1203 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "Expression de filtre non valide : %(filter)s" -#: awx/main/models/inventory.py:1212 +#: awx/main/models/inventory.py:1224 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "Choix de regroupement non valide : %(choice)s" -#: awx/main/models/inventory.py:1247 +#: awx/main/models/inventory.py:1259 msgid "Project containing inventory file used as source." msgstr "Projet contenant le fichier d'inventaire utilisé comme source." -#: awx/main/models/inventory.py:1395 +#: awx/main/models/inventory.py:1407 #, python-format msgid "" "Unable to configure this item for cloud sync. It is already managed by %s." @@ -2744,7 +2802,7 @@ msgstr "" "Impossible de configurer cet élément pour la synchronisation dans le cloud. " "Il est déjà géré par %s." -#: awx/main/models/inventory.py:1405 +#: awx/main/models/inventory.py:1417 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." @@ -2752,7 +2810,7 @@ msgstr "" "On n'autorise pas plus d'une source d'inventaire basé SCM avec mise à jour " "pré-inventaire ou mise à jour projet." -#: awx/main/models/inventory.py:1412 +#: awx/main/models/inventory.py:1424 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " @@ -2763,30 +2821,30 @@ msgstr "" " la place, configurez le projet source correspondant pour qu'il se mette à " "jour au moment du lancement." -#: awx/main/models/inventory.py:1418 +#: awx/main/models/inventory.py:1430 msgid "SCM type sources must set `overwrite_vars` to `true`." msgstr "Les sources de type SCM doivent définir `overwrite_vars` sur`true`." -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1435 msgid "Cannot set source_path if not SCM type." msgstr "Impossible de définir chemin_source si pas du type SCM." -#: awx/main/models/inventory.py:1448 +#: awx/main/models/inventory.py:1460 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "" "Les fichiers d'inventaire de cette mise à jour de projet ont été utilisés " "pour la mise à jour de l'inventaire." -#: awx/main/models/inventory.py:1561 +#: awx/main/models/inventory.py:1573 msgid "Inventory script contents" msgstr "Contenus des scripts d'inventaire" -#: awx/main/models/inventory.py:1566 +#: awx/main/models/inventory.py:1578 msgid "Organization owning this inventory script" msgstr "Organisation propriétaire de ce script d'inventaire." -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:66 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" @@ -2795,7 +2853,7 @@ msgstr "" "fichiers basés sur un modèle de l'hôte sont affichées dans la sortie " "standard out" -#: awx/main/models/jobs.py:163 +#: awx/main/models/jobs.py:164 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" " at the end of a playbook run to the database and caching facts for use by " @@ -2805,38 +2863,38 @@ msgstr "" " ; les faits persistants à la fin d'un playbook s'exécutent vers la base de " "données et les faits mis en cache pour être utilisés par Ansible." -#: awx/main/models/jobs.py:172 +#: awx/main/models/jobs.py:173 msgid "You must provide an SSH credential." msgstr "Vous devez fournir des informations d'identification SSH." -#: awx/main/models/jobs.py:180 +#: awx/main/models/jobs.py:181 msgid "You must provide a Vault credential." msgstr "Vous devez fournir des informations d'identification du coffre-fort." -#: awx/main/models/jobs.py:316 +#: awx/main/models/jobs.py:317 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "" "Le modèle de tâche doit fournir un inventaire ou permettre d'en demander un." -#: awx/main/models/jobs.py:320 +#: awx/main/models/jobs.py:321 msgid "Job Template must provide 'credential' or allow prompting for it." msgstr "" "Le modèle de tâche doit fournir des informations d'identification ou " "permettre d'en demander." -#: awx/main/models/jobs.py:422 +#: awx/main/models/jobs.py:427 msgid "Cannot override job_type to or from a scan job." msgstr "Impossible de remplacer job_type vers ou depuis une tâche de scan." -#: awx/main/models/jobs.py:488 awx/main/models/projects.py:263 +#: awx/main/models/jobs.py:493 awx/main/models/projects.py:263 msgid "SCM Revision" msgstr "Révision SCM" -#: awx/main/models/jobs.py:489 +#: awx/main/models/jobs.py:494 msgid "The SCM Revision from the Project used for this job, if available" msgstr "Révision SCM du projet utilisé pour cette tâche, le cas échéant" -#: awx/main/models/jobs.py:497 +#: awx/main/models/jobs.py:502 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" @@ -2844,105 +2902,105 @@ msgstr "" "Activité d'actualisation du SCM qui permet de s'assurer que les playbooks " "étaient disponibles pour l'exécution de la tâche" -#: awx/main/models/jobs.py:802 +#: awx/main/models/jobs.py:809 msgid "job host summaries" msgstr "récapitulatifs des hôtes pour la tâche" -#: awx/main/models/jobs.py:906 +#: awx/main/models/jobs.py:913 msgid "Host Failure" msgstr "Échec de l'hôte" -#: awx/main/models/jobs.py:909 awx/main/models/jobs.py:923 +#: awx/main/models/jobs.py:916 awx/main/models/jobs.py:930 msgid "No Hosts Remaining" msgstr "Aucun hôte restant" -#: awx/main/models/jobs.py:910 +#: awx/main/models/jobs.py:917 msgid "Host Polling" msgstr "Interrogation de l'hôte" -#: awx/main/models/jobs.py:911 +#: awx/main/models/jobs.py:918 msgid "Host Async OK" msgstr "Désynchronisation des hôtes OK" -#: awx/main/models/jobs.py:912 +#: awx/main/models/jobs.py:919 msgid "Host Async Failure" msgstr "Échec de désynchronisation des hôtes" -#: awx/main/models/jobs.py:913 +#: awx/main/models/jobs.py:920 msgid "Item OK" msgstr "Élément OK" -#: awx/main/models/jobs.py:914 +#: awx/main/models/jobs.py:921 msgid "Item Failed" msgstr "Échec de l'élément" -#: awx/main/models/jobs.py:915 +#: awx/main/models/jobs.py:922 msgid "Item Skipped" msgstr "Élément ignoré" -#: awx/main/models/jobs.py:916 +#: awx/main/models/jobs.py:923 msgid "Host Retry" msgstr "Nouvel essai de l'hôte" -#: awx/main/models/jobs.py:918 +#: awx/main/models/jobs.py:925 msgid "File Difference" msgstr "Écart entre les fichiers" -#: awx/main/models/jobs.py:919 +#: awx/main/models/jobs.py:926 msgid "Playbook Started" msgstr "Playbook démarré" -#: awx/main/models/jobs.py:920 +#: awx/main/models/jobs.py:927 msgid "Running Handlers" msgstr "Descripteurs d'exécution" -#: awx/main/models/jobs.py:921 +#: awx/main/models/jobs.py:928 msgid "Including File" msgstr "Ajout de fichier" -#: awx/main/models/jobs.py:922 +#: awx/main/models/jobs.py:929 msgid "No Hosts Matched" msgstr "Aucun hôte correspondant" -#: awx/main/models/jobs.py:924 +#: awx/main/models/jobs.py:931 msgid "Task Started" msgstr "Tâche démarrée" -#: awx/main/models/jobs.py:926 +#: awx/main/models/jobs.py:933 msgid "Variables Prompted" msgstr "Variables demandées" -#: awx/main/models/jobs.py:927 +#: awx/main/models/jobs.py:934 msgid "Gathering Facts" msgstr "Collecte des faits" -#: awx/main/models/jobs.py:928 +#: awx/main/models/jobs.py:935 msgid "internal: on Import for Host" msgstr "interne : à l'importation pour l'hôte" -#: awx/main/models/jobs.py:929 +#: awx/main/models/jobs.py:936 msgid "internal: on Not Import for Host" msgstr "interne : à la non-importation pour l'hôte" -#: awx/main/models/jobs.py:930 +#: awx/main/models/jobs.py:937 msgid "Play Started" msgstr "Scène démarrée" -#: awx/main/models/jobs.py:931 +#: awx/main/models/jobs.py:938 msgid "Playbook Complete" msgstr "Playbook terminé" -#: awx/main/models/jobs.py:1363 +#: awx/main/models/jobs.py:1351 msgid "Remove jobs older than a certain number of days" msgstr "Supprimer les tâches plus anciennes qu'un certain nombre de jours" -#: awx/main/models/jobs.py:1364 +#: awx/main/models/jobs.py:1352 msgid "Remove activity stream entries older than a certain number of days" msgstr "" "Supprimer les entrées du flux d'activité plus anciennes qu'un certain nombre" " de jours" -#: awx/main/models/jobs.py:1365 +#: awx/main/models/jobs.py:1353 msgid "Purge and/or reduce the granularity of system tracking data" msgstr "Purger et/ou réduire la granularité des données de suivi du système" @@ -3366,7 +3424,7 @@ msgstr "Exception lors de la connexion à Twilio : {}" msgid "Error sending notification webhook: {}" msgstr "Erreur lors de l'envoi d'un webhook de notification : {}" -#: awx/main/scheduler/__init__.py:184 +#: awx/main/scheduler/task_manager.py:197 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" @@ -3375,7 +3433,7 @@ msgstr "" "dans l'état qui convient ou nécessitant des informations d'identification " "manuelles adéquates." -#: awx/main/scheduler/__init__.py:188 +#: awx/main/scheduler/task_manager.py:201 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" @@ -3395,7 +3453,7 @@ msgstr "La licence Ansible Tower expirera bientôt" msgid "status_str must be either succeeded or failed" msgstr "status_str doit être une réussite ou un échec" -#: awx/main/tasks.py:1531 +#: awx/main/tasks.py:1549 msgid "Dependent inventory update {} was canceled." msgstr "La mise à jour d'inventaire dépendante {} a été annulée." @@ -3404,38 +3462,38 @@ msgstr "La mise à jour d'inventaire dépendante {} a été annulée." msgid "Unable to convert \"%s\" to boolean" msgstr "Impossible de convertir \"%s\" en booléen" -#: awx/main/utils/common.py:209 +#: awx/main/utils/common.py:235 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "Type de SCM \"%s\" non pris en charge" -#: awx/main/utils/common.py:216 awx/main/utils/common.py:228 -#: awx/main/utils/common.py:247 +#: awx/main/utils/common.py:242 awx/main/utils/common.py:254 +#: awx/main/utils/common.py:273 #, python-format msgid "Invalid %s URL" msgstr "URL %s non valide." -#: awx/main/utils/common.py:218 awx/main/utils/common.py:257 +#: awx/main/utils/common.py:244 awx/main/utils/common.py:283 #, python-format msgid "Unsupported %s URL" msgstr "URL %s non prise en charge" -#: awx/main/utils/common.py:259 +#: awx/main/utils/common.py:285 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "Hôte \"%s\" non pris en charge pour le fichier ://URL" -#: awx/main/utils/common.py:261 +#: awx/main/utils/common.py:287 #, python-format msgid "Host is required for %s URL" msgstr "L'hôte est requis pour l'URL %s" -#: awx/main/utils/common.py:279 +#: awx/main/utils/common.py:305 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "Le nom d'utilisateur doit être \"git\" pour l'accès SSH à %s." -#: awx/main/utils/common.py:285 +#: awx/main/utils/common.py:311 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "Le nom d'utilisateur doit être \"hg\" pour l'accès SSH à %s." @@ -3556,287 +3614,287 @@ msgstr "Erreur serveur" msgid "A server error has occurred." msgstr "Une erreur serveur s'est produite." -#: awx/settings/defaults.py:664 +#: awx/settings/defaults.py:665 msgid "US East (Northern Virginia)" msgstr "Est des États-Unis (Virginie du Nord)" -#: awx/settings/defaults.py:665 +#: awx/settings/defaults.py:666 msgid "US East (Ohio)" msgstr "Est des États-Unis (Ohio)" -#: awx/settings/defaults.py:666 +#: awx/settings/defaults.py:667 msgid "US West (Oregon)" msgstr "Ouest des États-Unis (Oregon)" -#: awx/settings/defaults.py:667 +#: awx/settings/defaults.py:668 msgid "US West (Northern California)" msgstr "Ouest des États-Unis (Nord de la Californie)" -#: awx/settings/defaults.py:668 +#: awx/settings/defaults.py:669 msgid "Canada (Central)" msgstr "Canada (Central)" -#: awx/settings/defaults.py:669 +#: awx/settings/defaults.py:670 msgid "EU (Frankfurt)" msgstr "UE (Francfort)" -#: awx/settings/defaults.py:670 +#: awx/settings/defaults.py:671 msgid "EU (Ireland)" msgstr "UE (Irlande)" -#: awx/settings/defaults.py:671 +#: awx/settings/defaults.py:672 msgid "EU (London)" msgstr "UE (Londres)" -#: awx/settings/defaults.py:672 +#: awx/settings/defaults.py:673 msgid "Asia Pacific (Singapore)" msgstr "Asie-Pacifique (Singapour)" -#: awx/settings/defaults.py:673 +#: awx/settings/defaults.py:674 msgid "Asia Pacific (Sydney)" msgstr "Asie-Pacifique (Sydney)" -#: awx/settings/defaults.py:674 +#: awx/settings/defaults.py:675 msgid "Asia Pacific (Tokyo)" msgstr "Asie-Pacifique (Tokyo)" -#: awx/settings/defaults.py:675 +#: awx/settings/defaults.py:676 msgid "Asia Pacific (Seoul)" msgstr "Asie-Pacifique (Séoul)" -#: awx/settings/defaults.py:676 +#: awx/settings/defaults.py:677 msgid "Asia Pacific (Mumbai)" msgstr "Asie-Pacifique (Mumbai)" -#: awx/settings/defaults.py:677 +#: awx/settings/defaults.py:678 msgid "South America (Sao Paulo)" msgstr "Amérique du Sud (Sao Paulo)" -#: awx/settings/defaults.py:678 +#: awx/settings/defaults.py:679 msgid "US West (GovCloud)" msgstr "Ouest des États-Unis (GovCloud)" -#: awx/settings/defaults.py:679 +#: awx/settings/defaults.py:680 msgid "China (Beijing)" msgstr "Chine (Pékin)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:729 msgid "US East 1 (B)" msgstr "Est des États-Unis 1 (B)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:730 msgid "US East 1 (C)" msgstr "Est des États-Unis 1 (C)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:731 msgid "US East 1 (D)" msgstr "Est des États-Unis 1 (D)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:732 msgid "US East 4 (A)" msgstr "Est des États-Unis 4 (A)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:733 msgid "US East 4 (B)" msgstr "Est des États-Unis 4 (B)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:734 msgid "US East 4 (C)" msgstr "Est des États-Unis 4 (C)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:735 msgid "US Central (A)" msgstr "Centre des États-Unis (A)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:736 msgid "US Central (B)" msgstr "Centre des États-Unis (B)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:737 msgid "US Central (C)" msgstr "Centre des États-Unis (C)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:738 msgid "US Central (F)" msgstr "Centre des États-Unis (F)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:739 msgid "US West (A)" msgstr "Ouest des États-Unis (A)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:740 msgid "US West (B)" msgstr "Ouest des États-Unis (B)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:741 msgid "US West (C)" msgstr "Ouest des États-Unis (C)" -#: awx/settings/defaults.py:741 +#: awx/settings/defaults.py:742 msgid "Europe West 1 (B)" msgstr "Europe de l'Ouest 1 (B)" -#: awx/settings/defaults.py:742 +#: awx/settings/defaults.py:743 msgid "Europe West 1 (C)" msgstr "Europe de l'Ouest 1 (C)" -#: awx/settings/defaults.py:743 +#: awx/settings/defaults.py:744 msgid "Europe West 1 (D)" msgstr "Europe de l'Ouest 1 (D)" -#: awx/settings/defaults.py:744 +#: awx/settings/defaults.py:745 msgid "Europe West 2 (A)" msgstr "Europe de l'Ouest 2 (A)" -#: awx/settings/defaults.py:745 +#: awx/settings/defaults.py:746 msgid "Europe West 2 (B)" msgstr "Europe de l'Ouest 2 (B)" -#: awx/settings/defaults.py:746 +#: awx/settings/defaults.py:747 msgid "Europe West 2 (C)" msgstr "Europe de l'Ouest 2 (C)" -#: awx/settings/defaults.py:747 +#: awx/settings/defaults.py:748 msgid "Asia East (A)" msgstr "Asie de l'Est (A)" -#: awx/settings/defaults.py:748 +#: awx/settings/defaults.py:749 msgid "Asia East (B)" msgstr "Asie de l'Est (B)" -#: awx/settings/defaults.py:749 +#: awx/settings/defaults.py:750 msgid "Asia East (C)" msgstr "Asie de l'Est (C)" -#: awx/settings/defaults.py:750 +#: awx/settings/defaults.py:751 msgid "Asia Southeast (A)" msgstr "Asie du Sud-Est (A)" -#: awx/settings/defaults.py:751 +#: awx/settings/defaults.py:752 msgid "Asia Southeast (B)" msgstr "Asie du Sud-Est (B)" -#: awx/settings/defaults.py:752 +#: awx/settings/defaults.py:753 msgid "Asia Northeast (A)" msgstr "Asie du Nord-Est (A)" -#: awx/settings/defaults.py:753 +#: awx/settings/defaults.py:754 msgid "Asia Northeast (B)" msgstr "Asie du Nord-Est (B)" -#: awx/settings/defaults.py:754 +#: awx/settings/defaults.py:755 msgid "Asia Northeast (C)" msgstr "Asie du Nord-Est (C)" -#: awx/settings/defaults.py:755 +#: awx/settings/defaults.py:756 msgid "Australia Southeast (A)" msgstr "Sud-est de l'Australie (A)" -#: awx/settings/defaults.py:756 +#: awx/settings/defaults.py:757 msgid "Australia Southeast (B)" msgstr "Sud-est de l'Australie (B)" -#: awx/settings/defaults.py:757 +#: awx/settings/defaults.py:758 msgid "Australia Southeast (C)" msgstr "Sud-est de l'Australie (C)" -#: awx/settings/defaults.py:781 +#: awx/settings/defaults.py:780 msgid "US East" msgstr "Est des États-Unis" -#: awx/settings/defaults.py:782 +#: awx/settings/defaults.py:781 msgid "US East 2" msgstr "Est des États-Unis 2" -#: awx/settings/defaults.py:783 +#: awx/settings/defaults.py:782 msgid "US Central" msgstr "Centre des États-Unis" -#: awx/settings/defaults.py:784 +#: awx/settings/defaults.py:783 msgid "US North Central" msgstr "Centre-Nord des États-Unis" -#: awx/settings/defaults.py:785 +#: awx/settings/defaults.py:784 msgid "US South Central" msgstr "Centre-Sud des États-Unis" -#: awx/settings/defaults.py:786 +#: awx/settings/defaults.py:785 msgid "US West Central" msgstr "Centre-Ouest des États-Unis" -#: awx/settings/defaults.py:787 +#: awx/settings/defaults.py:786 msgid "US West" msgstr "Ouest des États-Unis" -#: awx/settings/defaults.py:788 +#: awx/settings/defaults.py:787 msgid "US West 2" msgstr "Ouest des États-Unis 2" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:788 msgid "Canada East" msgstr "Est du Canada" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:789 msgid "Canada Central" msgstr "Centre du Canada" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:790 msgid "Brazil South" msgstr "Sud du Brésil" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:791 msgid "Europe North" msgstr "Europe du Nord" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:792 msgid "Europe West" msgstr "Europe de l'Ouest" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:793 msgid "UK West" msgstr "Ouest du Royaume-Uni" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:794 msgid "UK South" msgstr "Sud du Royaume-Uni" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:795 msgid "Asia East" msgstr "Asie de l'Est" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:796 msgid "Asia Southeast" msgstr "Asie du Sud-Est" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:797 msgid "Australia East" msgstr "Est de l'Australie" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:798 msgid "Australia Southeast" msgstr "Sud-Est de l'Australie" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:799 msgid "India West" msgstr "Ouest de l'Inde" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:800 msgid "India South" msgstr "Sud de l'Inde" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:801 msgid "Japan East" msgstr "Est du Japon" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:802 msgid "Japan West" msgstr "Ouest du Japon" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:803 msgid "Korea Central" msgstr "Centre de la Corée" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:804 msgid "Korea South" msgstr "Sud de la Corée" @@ -4593,6 +4651,7 @@ msgid "SAML Team Map" msgstr "Mappe d'équipes SAML" #: awx/sso/fields.py:123 +#, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "Option(s) de connexion non valide(s) : {invalid_options}." @@ -4609,18 +4668,21 @@ msgid "Subtree" msgstr "Sous-arborescence" #: awx/sso/fields.py:214 +#, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "" "Une liste de trois éléments était attendue, mais {length} a été obtenu à la " "place." #: awx/sso/fields.py:215 +#, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" "Une instance de LDAPSearch était attendue, mais {input_type} a été obtenu à " "la place." #: awx/sso/fields.py:251 +#, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." @@ -4629,20 +4691,24 @@ msgstr "" "{input_type} a été obtenu à la place." #: awx/sso/fields.py:289 +#, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "Attribut(s) d'utilisateur non valide(s) : {invalid_attrs}." #: awx/sso/fields.py:306 +#, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" "Une instance de LDAPGroupType était attendue, mais {input_type} a été obtenu" " à la place." #: awx/sso/fields.py:334 +#, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "Marqueur d'utilisateur non valide : \"{invalid_flag}\"." #: awx/sso/fields.py:350 awx/sso/fields.py:517 +#, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type}" " instead." @@ -4651,50 +4717,61 @@ msgstr "" "attendues, mais {input_type} a été obtenu à la place." #: awx/sso/fields.py:386 +#, python-brace-format msgid "Missing key(s): {missing_keys}." msgstr "Clé(s) manquante(s) : {missing_keys}." #: awx/sso/fields.py:387 +#, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "Clé(s) non valide(s) : {invalid_keys}." #: awx/sso/fields.py:436 awx/sso/fields.py:553 +#, python-brace-format msgid "Invalid key(s) for organization map: {invalid_keys}." msgstr "Clé(s) non valide(s) pour la mappe d'organisations : {invalid_keys}." #: awx/sso/fields.py:454 +#, python-brace-format msgid "Missing required key for team map: {invalid_keys}." msgstr "Clé obligatoire manquante pour la mappe d'équipes : {invalid_keys}." #: awx/sso/fields.py:455 awx/sso/fields.py:572 +#, python-brace-format msgid "Invalid key(s) for team map: {invalid_keys}." msgstr "Clé(s) non valide(s) pour la mappe d'équipes : {invalid_keys}." #: awx/sso/fields.py:571 +#, python-brace-format msgid "Missing required key for team map: {missing_keys}." msgstr "Clé obligatoire manquante pour la mappe d'équipes : {missing_keys}." #: awx/sso/fields.py:589 +#, python-brace-format msgid "Missing required key(s) for org info record: {missing_keys}." msgstr "" "Clé(s) obligatoire(s) manquante(s) pour l'enregistrement des infos organis. " ": {missing_keys}." #: awx/sso/fields.py:602 +#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "" "Code(s) langage non valide(s) pour les infos organis. : " "{invalid_lang_codes}." #: awx/sso/fields.py:621 +#, python-brace-format msgid "Missing required key(s) for contact: {missing_keys}." msgstr "Clé(s) obligatoire(s) manquante(s) pour le contact : {missing_keys}." #: awx/sso/fields.py:633 +#, python-brace-format msgid "Missing required key(s) for IdP: {missing_keys}." msgstr "Clé(s) obligatoire(s) manquante(s) pour l'IdP : {missing_keys}." #: awx/sso/pipeline.py:24 +#, python-brace-format msgid "An account cannot be found for {0}" msgstr "Impossible de trouver un compte pour {0}" @@ -4790,6 +4867,7 @@ msgid "Make a PATCH request on the %(name)s resource" msgstr "Appliquez une requête PATCH sur la ressource %(name)s" #: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 +#: awx/ui/conf.py:63 msgid "UI" msgstr "IU" @@ -4844,6 +4922,17 @@ msgstr "" " un meilleur résultat, utiliser un fichier .png avec un fond transparent. " "Les formats GIF, PNG et JPEG sont supportés." +#: awx/ui/conf.py:60 +msgid "Max Job Events Retreived by UI" +msgstr "Max Événements de tâches par UI" + +#: awx/ui/conf.py:61 +msgid "" +"Maximum number of job events for the UI to retreive within a single request." +msgstr "" +"Nombre maximum d'événements de tâches d'UI à extraire pour une requête " +"unique." + #: awx/ui/fields.py:29 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " diff --git a/awx/locale/ja/LC_MESSAGES/django.po b/awx/locale/ja/LC_MESSAGES/django.po index 4ee6bbdecd..a86ce49b9f 100644 --- a/awx/locale/ja/LC_MESSAGES/django.po +++ b/awx/locale/ja/LC_MESSAGES/django.po @@ -4,8 +4,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-08-27 19:27+0000\n" -"PO-Revision-Date: 2017-09-15 11:22+0000\n" +"POT-Creation-Date: 2017-11-30 20:23+0000\n" +"PO-Revision-Date: 2017-12-03 11:01+0000\n" "Last-Translator: asasaki \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -13,7 +13,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: ja\n" "Plural-Forms: nplurals=1; plural=0\n" -"X-Generator: Zanata 4.2.1\n" +"X-Generator: Zanata 4.3.2\n" #: awx/api/authentication.py:67 msgid "Invalid token header. No credentials provided." @@ -76,25 +76,30 @@ msgstr "%s でのフィルターは許可されていません。" msgid "Loops not allowed in filters, detected on field {}." msgstr "ループがフィルターで許可されていません。フィールド {} で検出されました。" -#: awx/api/filters.py:297 +#: awx/api/filters.py:171 +#, python-brace-format +msgid "Invalid {field_name} id: {field_id}" +msgstr "無効な {field_name} ID: {field_id}" + +#: awx/api/filters.py:302 #, python-format msgid "cannot filter on kind %s" msgstr "%s の種類でフィルターできません。" -#: awx/api/filters.py:404 +#: awx/api/filters.py:409 #, python-format msgid "cannot order by field %s" msgstr "フィールド %s で順序付けできません" -#: awx/api/generics.py:515 awx/api/generics.py:577 +#: awx/api/generics.py:550 awx/api/generics.py:612 msgid "\"id\" field must be an integer." msgstr "「id」フィールドは整数でなければなりません。" -#: awx/api/generics.py:574 +#: awx/api/generics.py:609 msgid "\"id\" is required to disassociate" msgstr "関連付けを解除するには 「id」が必要です" -#: awx/api/generics.py:625 +#: awx/api/generics.py:660 msgid "{} 'id' field is missing." msgstr "{} 「id」フィールドがありません。" @@ -175,7 +180,7 @@ msgstr "ワークフロージョブ" msgid "Workflow Template" msgstr "ワークフローテンプレート" -#: awx/api/serializers.py:697 awx/api/serializers.py:755 awx/api/views.py:4314 +#: awx/api/serializers.py:701 awx/api/serializers.py:759 awx/api/views.py:4365 #, python-format msgid "" "Standard Output too large to display (%(text_size)d bytes), only download " @@ -184,276 +189,290 @@ msgstr "" "標準出力が大きすぎて表示できません (%(text_size)d バイト)。サイズが %(supported_size)d " "バイトを超える場合はダウンロードのみがサポートされます。" -#: awx/api/serializers.py:770 +#: awx/api/serializers.py:774 msgid "Write-only field used to change the password." msgstr "パスワードを変更するために使用される書き込み専用フィールド。" -#: awx/api/serializers.py:772 +#: awx/api/serializers.py:776 msgid "Set if the account is managed by an external service" msgstr "アカウントが外部サービスで管理される場合に設定されます" -#: awx/api/serializers.py:796 +#: awx/api/serializers.py:800 msgid "Password required for new User." msgstr "新規ユーザーのパスワードを入力してください。" -#: awx/api/serializers.py:882 +#: awx/api/serializers.py:886 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "LDAP で管理されたユーザーの %s を変更できません。" -#: awx/api/serializers.py:1046 +#: awx/api/serializers.py:1050 msgid "Organization is missing" msgstr "組織がありません" -#: awx/api/serializers.py:1050 +#: awx/api/serializers.py:1054 msgid "Update options must be set to false for manual projects." msgstr "手動プロジェクトについては更新オプションを false に設定する必要があります。" -#: awx/api/serializers.py:1056 +#: awx/api/serializers.py:1060 msgid "Array of playbooks available within this project." msgstr "このプロジェクト内で利用可能な一連の Playbook。" -#: awx/api/serializers.py:1075 +#: awx/api/serializers.py:1079 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." msgstr "このプロジェクト内で利用可能な一連のインベントリーファイルおよびディレクトリー (包括的な一覧ではありません)。" -#: awx/api/serializers.py:1282 +#: awx/api/serializers.py:1201 +msgid "Smart inventories must specify host_filter" +msgstr "スマートインベントリーは host_filter を指定する必要があります" + +#: awx/api/serializers.py:1303 #, python-format msgid "Invalid port specification: %s" msgstr "無効なポート指定: %s" -#: awx/api/serializers.py:1293 +#: awx/api/serializers.py:1314 msgid "Cannot create Host for Smart Inventory" msgstr "スマートインベントリーのホストを作成できません" -#: awx/api/serializers.py:1315 awx/api/serializers.py:3218 -#: awx/api/serializers.py:3303 awx/main/validators.py:198 +#: awx/api/serializers.py:1336 awx/api/serializers.py:3321 +#: awx/api/serializers.py:3406 awx/main/validators.py:198 msgid "Must be valid JSON or YAML." msgstr "有効な JSON または YAML である必要があります。" -#: awx/api/serializers.py:1411 +#: awx/api/serializers.py:1432 msgid "Invalid group name." msgstr "無効なグループ名。" -#: awx/api/serializers.py:1416 +#: awx/api/serializers.py:1437 msgid "Cannot create Group for Smart Inventory" msgstr "スマートインベントリーのグループを作成できません" -#: awx/api/serializers.py:1488 +#: awx/api/serializers.py:1509 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "スクリプトは hashbang シーケンスで開始する必要があります (例: .... #!/usr/bin/env python)" -#: awx/api/serializers.py:1534 +#: awx/api/serializers.py:1555 msgid "`{}` is a prohibited environment variable" msgstr "`{}` は禁止されている環境変数です" -#: awx/api/serializers.py:1545 +#: awx/api/serializers.py:1566 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "「source」が「custom」である場合、「source_script」を指定する必要があります。" -#: awx/api/serializers.py:1551 +#: awx/api/serializers.py:1572 msgid "Must provide an inventory." msgstr "インベントリーを指定する必要があります。" -#: awx/api/serializers.py:1555 +#: awx/api/serializers.py:1576 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "「source_script」はインベントリーと同じ組織に属しません。" -#: awx/api/serializers.py:1557 +#: awx/api/serializers.py:1578 msgid "'source_script' doesn't exist." msgstr "「source_script」は存在しません。" -#: awx/api/serializers.py:1581 +#: awx/api/serializers.py:1602 msgid "Automatic group relationship, will be removed in 3.3" msgstr "自動的なグループ関係は 3.3 で削除されます" -#: awx/api/serializers.py:1658 +#: awx/api/serializers.py:1679 msgid "Cannot use manual project for SCM-based inventory." msgstr "SCM ベースのインベントリーの手動プロジェクトを使用できません。" -#: awx/api/serializers.py:1664 +#: awx/api/serializers.py:1685 msgid "" "Manual inventory sources are created automatically when a group is created " "in the v1 API." msgstr "手動のインベントリーソースは、グループが v1 API で作成される際に自動作成されます。" -#: awx/api/serializers.py:1669 +#: awx/api/serializers.py:1690 msgid "Setting not compatible with existing schedules." msgstr "設定は既存スケジュールとの互換性がありません。" -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1695 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "スマートインベントリーのインベントリーソースを作成できません" -#: awx/api/serializers.py:1688 +#: awx/api/serializers.py:1709 #, python-format msgid "Cannot set %s if not SCM type." msgstr "SCM タイプでない場合 %s を設定できません。" -#: awx/api/serializers.py:1929 +#: awx/api/serializers.py:1950 msgid "Modifications not allowed for managed credential types" msgstr "管理されている認証情報タイプで変更は許可されません" -#: awx/api/serializers.py:1934 +#: awx/api/serializers.py:1955 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "入力への変更は使用中の認証情報タイプで許可されません" -#: awx/api/serializers.py:1940 +#: awx/api/serializers.py:1961 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "「cloud」または「net」にする必要があります (%s ではない)" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1967 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "「ask_at_runtime」はカスタム認証情報ではサポートされません。" -#: awx/api/serializers.py:2119 +#: awx/api/serializers.py:2140 #, python-format msgid "\"%s\" is not a valid choice" msgstr "「%s」は有効な選択ではありません" -#: awx/api/serializers.py:2138 +#: awx/api/serializers.py:2159 #, python-format msgid "'%s' is not a valid field for %s" msgstr "「%s」は %s の有効なフィールドではありません" -#: awx/api/serializers.py:2150 +#: awx/api/serializers.py:2180 +msgid "" +"You cannot change the credential type of the credential, as it may break the" +" functionality of the resources using it." +msgstr "認証情報の認証情報タイプを変更することはできません。これにより、認証情報を使用するリソースの機能が中断する可能性があるためです。" + +#: awx/api/serializers.py:2191 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." msgstr "" "ユーザーを所有者ロールに追加するために使用される書き込み専用フィールドです。提供されている場合は、チームまたは組織のいずれも指定しないでください。作成時にのみ有効です。" -#: awx/api/serializers.py:2155 +#: awx/api/serializers.py:2196 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." msgstr "" "チームを所有者ロールに追加するために使用される書き込み専用フィールドです。提供されている場合は、ユーザーまたは組織のいずれも指定しないでください。作成時にのみ有効です。" -#: awx/api/serializers.py:2160 +#: awx/api/serializers.py:2201 msgid "" "Inherit permissions from organization roles. If provided on creation, do not" " give either user or team." msgstr "組織ロールからパーミッションを継承します。作成時に提供される場合は、ユーザーまたはチームのいずれも指定しないでください。" -#: awx/api/serializers.py:2176 +#: awx/api/serializers.py:2217 msgid "Missing 'user', 'team', or 'organization'." msgstr "「user」、「team」、または「organization」がありません。" -#: awx/api/serializers.py:2216 +#: awx/api/serializers.py:2257 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "認証情報の組織が設定され、一致している状態でチームに割り当てる必要があります。" -#: awx/api/serializers.py:2378 +#: awx/api/serializers.py:2424 msgid "You must provide a cloud credential." msgstr "クラウド認証情報を指定する必要があります。" -#: awx/api/serializers.py:2379 +#: awx/api/serializers.py:2425 msgid "You must provide a network credential." msgstr "ネットワーク認証情報を指定する必要があります。" -#: awx/api/serializers.py:2395 +#: awx/api/serializers.py:2441 msgid "This field is required." msgstr "このフィールドは必須です。" -#: awx/api/serializers.py:2397 awx/api/serializers.py:2399 +#: awx/api/serializers.py:2443 awx/api/serializers.py:2445 msgid "Playbook not found for project." msgstr "プロジェクトの Playbook が見つかりません。" -#: awx/api/serializers.py:2401 +#: awx/api/serializers.py:2447 msgid "Must select playbook for project." msgstr "プロジェクトの Playbook を選択してください。" -#: awx/api/serializers.py:2476 +#: awx/api/serializers.py:2522 msgid "Must either set a default value or ask to prompt on launch." msgstr "起動時にプロントを出すには、デフォルト値を設定するか、またはプロンプトを出すよう指定する必要があります。" -#: awx/api/serializers.py:2478 awx/main/models/jobs.py:325 +#: awx/api/serializers.py:2524 awx/main/models/jobs.py:326 msgid "Job types 'run' and 'check' must have assigned a project." msgstr "ジョブタイプ「run」および「check」によりプロジェクトが割り当てられている必要があります。" -#: awx/api/serializers.py:2549 +#: awx/api/serializers.py:2611 msgid "Invalid job template." msgstr "無効なジョブテンプレート。" -#: awx/api/serializers.py:2630 -msgid "Credential not found or deleted." -msgstr "認証情報が見つからないか、または削除されました。" +#: awx/api/serializers.py:2708 +msgid "Neither credential nor vault credential provided." +msgstr "認証情報および Vault 認証情報のいずれも指定されていません。" -#: awx/api/serializers.py:2632 +#: awx/api/serializers.py:2711 msgid "Job Template Project is missing or undefined." msgstr "ジョブテンプレートプロジェクトが見つからないか、または定義されていません。" -#: awx/api/serializers.py:2634 +#: awx/api/serializers.py:2713 msgid "Job Template Inventory is missing or undefined." msgstr "ジョブテンプレートインベントリーが見つからないか、または定義されていません。" -#: awx/api/serializers.py:2921 +#: awx/api/serializers.py:2782 awx/main/tasks.py:2186 +msgid "{} are prohibited from use in ad hoc commands." +msgstr "{} の使用はアドホックコマンドで禁止されています。" + +#: awx/api/serializers.py:3008 #, python-format msgid "%(job_type)s is not a valid job type. The choices are %(choices)s." msgstr "%(job_type)s は有効なジョブタイプではありません。%(choices)s を選択できます。" -#: awx/api/serializers.py:2926 +#: awx/api/serializers.py:3013 msgid "Workflow job template is missing during creation." msgstr "ワークフロージョブテンプレートが作成時に見つかりません。" -#: awx/api/serializers.py:2931 +#: awx/api/serializers.py:3018 #, python-format msgid "Cannot nest a %s inside a WorkflowJobTemplate" msgstr "ワークフロージョブテンプレート内に %s をネストできません" -#: awx/api/serializers.py:3188 +#: awx/api/serializers.py:3291 #, python-format msgid "Job Template '%s' is missing or undefined." msgstr "ジョブテンプレート「%s」が見つからない、または定義されていません。" -#: awx/api/serializers.py:3191 +#: awx/api/serializers.py:3294 msgid "The inventory associated with this Job Template is being deleted." msgstr "このジョブテンプレートに関連付けられているインベントリーが削除されています。" -#: awx/api/serializers.py:3232 awx/api/views.py:2991 +#: awx/api/serializers.py:3335 awx/api/views.py:3023 #, python-format msgid "Cannot assign multiple %s credentials." msgstr "複数の %s 認証情報を割り当てることができません。" -#: awx/api/serializers.py:3234 awx/api/views.py:2994 +#: awx/api/serializers.py:3337 awx/api/views.py:3026 msgid "Extra credentials must be network or cloud." msgstr "追加の認証情報はネットワークまたはクラウドにする必要があります。" -#: awx/api/serializers.py:3371 +#: awx/api/serializers.py:3474 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "通知設定の必須フィールドがありません: notification_type" -#: awx/api/serializers.py:3394 +#: awx/api/serializers.py:3497 msgid "No values specified for field '{}'" msgstr "フィールド '{}' に値が指定されていません" -#: awx/api/serializers.py:3399 +#: awx/api/serializers.py:3502 msgid "Missing required fields for Notification Configuration: {}." msgstr "通知設定の必須フィールドがありません: {}。" -#: awx/api/serializers.py:3402 +#: awx/api/serializers.py:3505 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "設定フィールド '{}' のタイプが正しくありません。{} が予期されました。" -#: awx/api/serializers.py:3455 +#: awx/api/serializers.py:3558 msgid "Inventory Source must be a cloud resource." msgstr "インベントリーソースはクラウドリソースでなければなりません。" -#: awx/api/serializers.py:3457 +#: awx/api/serializers.py:3560 msgid "Manual Project cannot have a schedule set." msgstr "手動プロジェクトにはスケジュールを設定できません。" -#: awx/api/serializers.py:3460 +#: awx/api/serializers.py:3563 msgid "" "Inventory sources with `update_on_project_update` cannot be scheduled. " "Schedule its source project `{}` instead." @@ -461,74 +480,74 @@ msgstr "" "「update_on_project_update」が設定されたインベントリーソースはスケジュールできません。代わりのそのソースプロジェクト「{}」 " "をスケジュールします。" -#: awx/api/serializers.py:3479 +#: awx/api/serializers.py:3582 msgid "Projects and inventory updates cannot accept extra variables." msgstr "プロジェクトおよびインベントリーの更新は追加変数を受け入れません。" -#: awx/api/serializers.py:3501 +#: awx/api/serializers.py:3604 msgid "" "DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ" msgstr "DTSTART が rrule で必要です。値は、DSTART:YYYYMMDDTHHMMSSZ に一致する必要があります。" -#: awx/api/serializers.py:3503 +#: awx/api/serializers.py:3606 msgid "Multiple DTSTART is not supported." msgstr "複数の DTSTART はサポートされません。" -#: awx/api/serializers.py:3505 +#: awx/api/serializers.py:3608 msgid "RRULE require in rrule." msgstr "RRULE が rrule で必要です。" -#: awx/api/serializers.py:3507 +#: awx/api/serializers.py:3610 msgid "Multiple RRULE is not supported." msgstr "複数の RRULE はサポートされません。" -#: awx/api/serializers.py:3509 +#: awx/api/serializers.py:3612 msgid "INTERVAL required in rrule." msgstr "INTERVAL が rrule で必要です。" -#: awx/api/serializers.py:3511 +#: awx/api/serializers.py:3614 msgid "TZID is not supported." msgstr "TZID はサポートされません。" -#: awx/api/serializers.py:3513 +#: awx/api/serializers.py:3616 msgid "SECONDLY is not supported." msgstr "SECONDLY はサポートされません。" -#: awx/api/serializers.py:3515 +#: awx/api/serializers.py:3618 msgid "Multiple BYMONTHDAYs not supported." msgstr "複数の BYMONTHDAY はサポートされません。" -#: awx/api/serializers.py:3517 +#: awx/api/serializers.py:3620 msgid "Multiple BYMONTHs not supported." msgstr "複数の BYMONTH はサポートされません。" -#: awx/api/serializers.py:3519 +#: awx/api/serializers.py:3622 msgid "BYDAY with numeric prefix not supported." msgstr "数字の接頭辞のある BYDAY はサポートされません。" -#: awx/api/serializers.py:3521 +#: awx/api/serializers.py:3624 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY はサポートされません。" -#: awx/api/serializers.py:3523 +#: awx/api/serializers.py:3626 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO はサポートされません。" -#: awx/api/serializers.py:3527 +#: awx/api/serializers.py:3630 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 はサポートされません。" -#: awx/api/serializers.py:3531 +#: awx/api/serializers.py:3634 msgid "rrule parsing failed validation." msgstr "rrule の構文解析で検証に失敗しました。" -#: awx/api/serializers.py:3631 +#: awx/api/serializers.py:3760 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" msgstr "オブジェクトの作成、更新または削除時の新規値および変更された値の概要" -#: awx/api/serializers.py:3633 +#: awx/api/serializers.py:3762 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " @@ -537,7 +556,7 @@ msgstr "" "作成、更新、および削除イベントの場合、これは影響を受けたオブジェクトタイプになります。関連付けおよび関連付け解除イベントの場合、これは object2 " "に関連付けられたか、またはその関連付けが解除されたオブジェクトタイプになります。" -#: awx/api/serializers.py:3636 +#: awx/api/serializers.py:3765 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated" @@ -546,252 +565,253 @@ msgstr "" "作成、更新、および削除イベントの場合は設定されません。関連付けおよび関連付け解除イベントの場合、これは object1 " "が関連付けられるオブジェクトタイプになります。" -#: awx/api/serializers.py:3639 +#: awx/api/serializers.py:3768 msgid "The action taken with respect to the given object(s)." msgstr "指定されたオブジェクトについて実行されたアクション。" -#: awx/api/serializers.py:3749 +#: awx/api/serializers.py:3885 msgid "Unable to login with provided credentials." msgstr "提供される認証情報でログインできません。" -#: awx/api/serializers.py:3751 +#: awx/api/serializers.py:3887 msgid "Must include \"username\" and \"password\"." msgstr "「username」および「password」を含める必要があります。" -#: awx/api/views.py:106 +#: awx/api/views.py:108 msgid "Your license does not allow use of the activity stream." msgstr "お使いのライセンスではアクティビティーストリームを使用できません。" -#: awx/api/views.py:116 +#: awx/api/views.py:118 msgid "Your license does not permit use of system tracking." msgstr "お使いのライセンスではシステムトラッキングを使用できません。" -#: awx/api/views.py:126 +#: awx/api/views.py:128 msgid "Your license does not allow use of workflows." msgstr "お使いのライセンスではワークフローを使用できません。" -#: awx/api/views.py:140 +#: awx/api/views.py:142 msgid "Cannot delete job resource when associated workflow job is running." msgstr "関連付けられたワークフロージョブが実行中の場合、ジョブリソースを削除できません。" -#: awx/api/views.py:144 +#: awx/api/views.py:146 msgid "Cannot delete running job resource." msgstr "実行中のジョブリソースを削除できません。" -#: awx/api/views.py:153 awx/templates/rest_framework/api.html:28 +#: awx/api/views.py:155 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "REST API" -#: awx/api/views.py:162 awx/templates/rest_framework/api.html:4 +#: awx/api/views.py:164 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "AWX REST API" -#: awx/api/views.py:224 +#: awx/api/views.py:226 msgid "Version 1" msgstr "バージョン 1" -#: awx/api/views.py:228 +#: awx/api/views.py:230 msgid "Version 2" msgstr "バージョン 2" -#: awx/api/views.py:239 +#: awx/api/views.py:241 msgid "Ping" msgstr "Ping" -#: awx/api/views.py:270 awx/conf/apps.py:12 +#: awx/api/views.py:272 awx/conf/apps.py:12 msgid "Configuration" msgstr "設定" -#: awx/api/views.py:323 +#: awx/api/views.py:325 msgid "Invalid license data" msgstr "無効なライセンスデータ" -#: awx/api/views.py:325 +#: awx/api/views.py:327 msgid "Missing 'eula_accepted' property" msgstr "'eula_accepted' プロパティーがありません" -#: awx/api/views.py:329 +#: awx/api/views.py:331 msgid "'eula_accepted' value is invalid" msgstr "'eula_accepted' 値は無効です。" -#: awx/api/views.py:332 +#: awx/api/views.py:334 msgid "'eula_accepted' must be True" msgstr "'eula_accepted' は True でなければなりません" -#: awx/api/views.py:339 +#: awx/api/views.py:341 msgid "Invalid JSON" msgstr "無効な JSON" -#: awx/api/views.py:347 +#: awx/api/views.py:349 msgid "Invalid License" msgstr "無効なライセンス" -#: awx/api/views.py:357 +#: awx/api/views.py:359 msgid "Invalid license" msgstr "無効なライセンス" -#: awx/api/views.py:365 +#: awx/api/views.py:367 #, python-format msgid "Failed to remove license (%s)" msgstr "ライセンスの削除に失敗しました (%s)" -#: awx/api/views.py:370 +#: awx/api/views.py:372 msgid "Dashboard" msgstr "ダッシュボード" -#: awx/api/views.py:469 +#: awx/api/views.py:471 msgid "Dashboard Jobs Graphs" msgstr "ダッシュボードのジョブグラフ" -#: awx/api/views.py:505 +#: awx/api/views.py:507 #, python-format msgid "Unknown period \"%s\"" msgstr "不明な期間 \"%s\"" -#: awx/api/views.py:519 +#: awx/api/views.py:521 msgid "Instances" msgstr "インスタンス" -#: awx/api/views.py:527 +#: awx/api/views.py:529 msgid "Instance Detail" msgstr "インスタンスの詳細" -#: awx/api/views.py:535 +#: awx/api/views.py:537 msgid "Instance Running Jobs" msgstr "ジョブを実行しているインスタンス" -#: awx/api/views.py:550 +#: awx/api/views.py:552 msgid "Instance's Instance Groups" msgstr "インスタンスのインスタンスグループ" -#: awx/api/views.py:560 +#: awx/api/views.py:562 msgid "Instance Groups" msgstr "インスタンスグループ" -#: awx/api/views.py:568 +#: awx/api/views.py:570 msgid "Instance Group Detail" msgstr "インスタンスグループの詳細" -#: awx/api/views.py:576 +#: awx/api/views.py:578 msgid "Instance Group Running Jobs" msgstr "ジョブを実行しているインスタンスグループ" -#: awx/api/views.py:586 +#: awx/api/views.py:588 msgid "Instance Group's Instances" msgstr "インスタンスグループのインスタンス" -#: awx/api/views.py:596 +#: awx/api/views.py:598 msgid "Schedules" msgstr "スケジュール" -#: awx/api/views.py:615 +#: awx/api/views.py:617 msgid "Schedule Jobs List" msgstr "スケジュールジョブの一覧" -#: awx/api/views.py:841 +#: awx/api/views.py:843 msgid "Your license only permits a single organization to exist." msgstr "お使いのライセンスでは、単一組織のみの存在が許可されます。" -#: awx/api/views.py:1077 awx/api/views.py:4615 +#: awx/api/views.py:1079 awx/api/views.py:4666 msgid "You cannot assign an Organization role as a child role for a Team." msgstr "組織ロールをチームの子ロールとして割り当てることができません。" -#: awx/api/views.py:1081 awx/api/views.py:4629 +#: awx/api/views.py:1083 awx/api/views.py:4680 msgid "You cannot grant system-level permissions to a team." msgstr "システムレベルのパーミッションをチームに付与できません。" -#: awx/api/views.py:1088 awx/api/views.py:4621 +#: awx/api/views.py:1090 awx/api/views.py:4672 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" msgstr "組織フィールドが設定されていないか、または別の組織に属する場合に認証情報のアクセス権をチームに付与できません" -#: awx/api/views.py:1178 +#: awx/api/views.py:1180 msgid "Cannot delete project." msgstr "プロジェクトを削除できません。" -#: awx/api/views.py:1213 +#: awx/api/views.py:1215 msgid "Project Schedules" msgstr "プロジェクトのスケジュール" -#: awx/api/views.py:1225 +#: awx/api/views.py:1227 msgid "Project SCM Inventory Sources" msgstr "プロジェクト SCM のインベントリーソース" -#: awx/api/views.py:1352 +#: awx/api/views.py:1354 msgid "Project Update SCM Inventory Updates" msgstr "プロジェクト更新 SCM のインベントリー更新" -#: awx/api/views.py:1407 +#: awx/api/views.py:1409 msgid "Me" msgstr "自分" -#: awx/api/views.py:1451 awx/api/views.py:4572 +#: awx/api/views.py:1453 awx/api/views.py:4623 msgid "You may not perform any action with your own admin_role." msgstr "独自の admin_role でアクションを実行することはできません。" -#: awx/api/views.py:1457 awx/api/views.py:4576 +#: awx/api/views.py:1459 awx/api/views.py:4627 msgid "You may not change the membership of a users admin_role" msgstr "ユーザーの admin_role のメンバーシップを変更することはできません" -#: awx/api/views.py:1462 awx/api/views.py:4581 +#: awx/api/views.py:1464 awx/api/views.py:4632 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" msgstr "認証情報の組織に属さないユーザーに認証情報のアクセス権を付与することはできません" -#: awx/api/views.py:1466 awx/api/views.py:4585 +#: awx/api/views.py:1468 awx/api/views.py:4636 msgid "You cannot grant private credential access to another user" msgstr "非公開の認証情報のアクセス権を別のユーザーに付与することはできません" -#: awx/api/views.py:1564 +#: awx/api/views.py:1566 #, python-format msgid "Cannot change %s." msgstr "%s を変更できません。" -#: awx/api/views.py:1570 +#: awx/api/views.py:1572 msgid "Cannot delete user." msgstr "ユーザーを削除できません。" -#: awx/api/views.py:1599 +#: awx/api/views.py:1601 msgid "Deletion not allowed for managed credential types" msgstr "管理されている認証情報タイプで削除は許可されません" -#: awx/api/views.py:1601 +#: awx/api/views.py:1603 msgid "Credential types that are in use cannot be deleted" msgstr "使用中の認証情報タイプを削除できません" -#: awx/api/views.py:1779 +#: awx/api/views.py:1781 msgid "Cannot delete inventory script." msgstr "インベントリースクリプトを削除できません。" -#: awx/api/views.py:1864 +#: awx/api/views.py:1866 +#, python-brace-format msgid "{0}" msgstr "{0}" -#: awx/api/views.py:2089 +#: awx/api/views.py:2101 msgid "Fact not found." msgstr "ファクトが見つかりませんでした。" -#: awx/api/views.py:2113 +#: awx/api/views.py:2125 msgid "SSLError while trying to connect to {}" msgstr "{} への接続試行中に SSL エラーが発生しました" -#: awx/api/views.py:2115 +#: awx/api/views.py:2127 msgid "Request to {} timed out." msgstr "{} の要求がタイムアウトになりました。" -#: awx/api/views.py:2117 +#: awx/api/views.py:2129 msgid "Unkown exception {} while trying to GET {}" msgstr "GET {} の試行中に不明の例外 {} が発生しました" -#: awx/api/views.py:2120 +#: awx/api/views.py:2132 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "不正アクセスです。Insights 認証情報のユーザー名およびパスワードを確認してください。" -#: awx/api/views.py:2122 +#: awx/api/views.py:2134 msgid "" "Failed to gather reports and maintenance plans from Insights API at URL {}. " "Server responded with {} status code and message {}" @@ -799,196 +819,210 @@ msgstr "" "URL {} で Insights API からのレポートおよびメンテナンス計画を収集できませんでした。サーバーが {} " "ステータスコードおよびメッセージ {} を出して応答しました。" -#: awx/api/views.py:2128 +#: awx/api/views.py:2140 msgid "Expected JSON response from Insights but instead got {}" msgstr "Insights からの JSON 応答を予想していましたが、代わりに {} を取得しました。" -#: awx/api/views.py:2135 +#: awx/api/views.py:2147 msgid "This host is not recognized as an Insights host." msgstr "このホストは Insights ホストとして認識されていません。" -#: awx/api/views.py:2140 +#: awx/api/views.py:2152 msgid "The Insights Credential for \"{}\" was not found." msgstr "\"{}\" の Insights 認証情報が見つかりませんでした。" -#: awx/api/views.py:2209 +#: awx/api/views.py:2221 msgid "Cyclical Group association." msgstr "循環的なグループの関連付け" -#: awx/api/views.py:2482 +#: awx/api/views.py:2499 msgid "Inventory Source List" msgstr "インベントリーソース一覧" -#: awx/api/views.py:2495 +#: awx/api/views.py:2512 msgid "Inventory Sources Update" msgstr "インベントリーソースの更新" -#: awx/api/views.py:2525 +#: awx/api/views.py:2542 msgid "Could not start because `can_update` returned False" msgstr "`can_update` が False を返したので開始できませんでした" -#: awx/api/views.py:2533 +#: awx/api/views.py:2550 msgid "No inventory sources to update." msgstr "更新するインベントリーソースがありません。" -#: awx/api/views.py:2565 +#: awx/api/views.py:2582 msgid "Cannot delete inventory source." msgstr "インベントリーソースを削除できません。" -#: awx/api/views.py:2573 +#: awx/api/views.py:2590 msgid "Inventory Source Schedules" msgstr "インベントリーソースのスケジュール" -#: awx/api/views.py:2603 +#: awx/api/views.py:2620 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "ソースが {} のいずれかである場合、通知テンプレートのみを割り当てることができます。" -#: awx/api/views.py:2826 +#: awx/api/views.py:2851 msgid "Job Template Schedules" msgstr "ジョブテンプレートスケジュール" -#: awx/api/views.py:2846 awx/api/views.py:2862 +#: awx/api/views.py:2871 awx/api/views.py:2882 msgid "Your license does not allow adding surveys." msgstr "お使いのライセンスでは Survey を追加できません。" -#: awx/api/views.py:2869 +#: awx/api/views.py:2889 msgid "'name' missing from survey spec." msgstr "Survey の指定に「name」がありません。" -#: awx/api/views.py:2871 +#: awx/api/views.py:2891 msgid "'description' missing from survey spec." msgstr "Survey の指定に「description」がありません。" -#: awx/api/views.py:2873 +#: awx/api/views.py:2893 msgid "'spec' missing from survey spec." msgstr "Survey の指定に「spec」がありません。" -#: awx/api/views.py:2875 +#: awx/api/views.py:2895 msgid "'spec' must be a list of items." msgstr "「spec」は項目の一覧にする必要があります。" -#: awx/api/views.py:2877 +#: awx/api/views.py:2897 msgid "'spec' doesn't contain any items." msgstr "「spec」には項目が含まれません。" -#: awx/api/views.py:2883 +#: awx/api/views.py:2903 #, python-format msgid "Survey question %s is not a json object." msgstr "Survey の質問 %s は json オブジェクトではありません。" -#: awx/api/views.py:2885 +#: awx/api/views.py:2905 #, python-format msgid "'type' missing from survey question %s." msgstr "Survey の質問 %s に「type」がありません。" -#: awx/api/views.py:2887 +#: awx/api/views.py:2907 #, python-format msgid "'question_name' missing from survey question %s." msgstr "Survey の質問 %s に「question_name」がありません。" -#: awx/api/views.py:2889 +#: awx/api/views.py:2909 #, python-format msgid "'variable' missing from survey question %s." msgstr "Survey の質問 %s に「variable」がありません。" -#: awx/api/views.py:2891 +#: awx/api/views.py:2911 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "Survey の質問%(survey)s で「variable」の「%(item)s」が重複しています。" -#: awx/api/views.py:2896 +#: awx/api/views.py:2916 #, python-format msgid "'required' missing from survey question %s." msgstr "Survey の質問 %s に「required」がありません。" -#: awx/api/views.py:2901 +#: awx/api/views.py:2921 +#, python-brace-format msgid "" -"$encrypted$ is reserved keyword and may not be used as a default for " -"password {}." -msgstr "$encrypted$ は予約されたキーワードで、パスワード {} のデフォルトして使用できません。" +"Value {question_default} for '{variable_name}' expected to be a string." +msgstr "'{variable_name}' の値 {question_default} は文字列であることが予想されます。" -#: awx/api/views.py:3017 +#: awx/api/views.py:2928 +#, python-brace-format +msgid "" +"$encrypted$ is reserved keyword for password questions and may not be used " +"as a default for '{variable_name}' in survey question {question_position}." +msgstr "" +"$encrypted$ はパスワードの質問の予約されたキーワードで、Survey の質問 {question_position} では " +"'{variable_name}' のデフォルトとして使用できません。" + +#: awx/api/views.py:3049 msgid "Maximum number of labels for {} reached." msgstr "{} のラベルの最大数に達しました。" -#: awx/api/views.py:3136 +#: awx/api/views.py:3170 msgid "No matching host could be found!" msgstr "一致するホストが見つかりませんでした!" -#: awx/api/views.py:3139 +#: awx/api/views.py:3173 msgid "Multiple hosts matched the request!" msgstr "複数のホストが要求に一致しました!" -#: awx/api/views.py:3144 +#: awx/api/views.py:3178 msgid "Cannot start automatically, user input required!" msgstr "自動的に開始できません。ユーザー入力が必要です!" -#: awx/api/views.py:3151 +#: awx/api/views.py:3185 msgid "Host callback job already pending." msgstr "ホストのコールバックジョブがすでに保留中です。" -#: awx/api/views.py:3164 +#: awx/api/views.py:3199 msgid "Error starting job!" msgstr "ジョブの開始時にエラーが発生しました!" -#: awx/api/views.py:3271 +#: awx/api/views.py:3306 +#, python-brace-format msgid "Cannot associate {0} when {1} have been associated." msgstr "{1} が関連付けられている場合に {0} を関連付けることはできません。" -#: awx/api/views.py:3296 +#: awx/api/views.py:3331 msgid "Multiple parent relationship not allowed." msgstr "複数の親関係は許可されません。" -#: awx/api/views.py:3301 +#: awx/api/views.py:3336 msgid "Cycle detected." msgstr "サイクルが検出されました。" -#: awx/api/views.py:3505 +#: awx/api/views.py:3540 msgid "Workflow Job Template Schedules" msgstr "ワークフロージョブテンプレートのスケジュール" -#: awx/api/views.py:3650 awx/api/views.py:4217 +#: awx/api/views.py:3685 awx/api/views.py:4268 msgid "Superuser privileges needed." msgstr "スーパーユーザー権限が必要です。" -#: awx/api/views.py:3682 +#: awx/api/views.py:3717 msgid "System Job Template Schedules" msgstr "システムジョブテンプレートのスケジュール" -#: awx/api/views.py:3891 +#: awx/api/views.py:3780 +msgid "POST not allowed for Job launching in version 2 of the api" +msgstr "POST は API のバージョン 2 でのジョブの起動では許可されません" + +#: awx/api/views.py:3942 msgid "Job Host Summaries List" msgstr "ジョブホスト概要一覧" -#: awx/api/views.py:3938 +#: awx/api/views.py:3989 msgid "Job Event Children List" msgstr "ジョブイベント子一覧" -#: awx/api/views.py:3947 +#: awx/api/views.py:3998 msgid "Job Event Hosts List" msgstr "ジョブイベントホスト一覧" -#: awx/api/views.py:3957 +#: awx/api/views.py:4008 msgid "Job Events List" msgstr "ジョブイベント一覧" -#: awx/api/views.py:4171 +#: awx/api/views.py:4222 msgid "Ad Hoc Command Events List" msgstr "アドホックコマンドイベント一覧" -#: awx/api/views.py:4386 +#: awx/api/views.py:4437 msgid "Error generating stdout download file: {}" msgstr "stdout ダウンロードファイルの生成中にエラーが発生しました: {}" -#: awx/api/views.py:4399 +#: awx/api/views.py:4450 #, python-format msgid "Error generating stdout download file: %s" msgstr "stdout ダウンロードファイルの生成中にエラーが発生しました: %s" -#: awx/api/views.py:4444 +#: awx/api/views.py:4495 msgid "Delete not allowed while there are pending notifications" msgstr "保留中の通知がある場合に削除は許可されません" -#: awx/api/views.py:4451 +#: awx/api/views.py:4502 msgid "Notification Template Test" msgstr "通知テンプレートテスト" @@ -1144,15 +1178,16 @@ msgstr "ユーザーごとに異なる設定例" msgid "User" msgstr "ユーザー" -#: awx/conf/fields.py:62 +#: awx/conf/fields.py:63 msgid "Enter a valid URL" msgstr "無効な URL の入力" -#: awx/conf/fields.py:94 +#: awx/conf/fields.py:95 +#, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input}\" は有効な文字列ではありません。" -#: awx/conf/license.py:19 +#: awx/conf/license.py:22 msgid "Your Tower license does not allow that." msgstr "お使いの Tower ライセンスではこれを許可しません。" @@ -1169,7 +1204,11 @@ msgstr "コメント/移行時にエラーを発生させる設定をスキッ msgid "Skip commenting out settings in files." msgstr "ファイル内の設定のコメント化をスキップします。" -#: awx/conf/management/commands/migrate_to_database_settings.py:61 +#: awx/conf/management/commands/migrate_to_database_settings.py:62 +msgid "Skip migrating and only comment out settings in files." +msgstr "移行をスキップし、ファイルの設定のみをコメントアウトします。" + +#: awx/conf/management/commands/migrate_to_database_settings.py:68 msgid "Backup existing settings files with this suffix." msgstr "この接尾辞を持つ既存の設定ファイルをバックアップします。" @@ -1193,7 +1232,7 @@ msgstr "変更済み" msgid "User-Defaults" msgstr "ユーザー設定" -#: awx/conf/registry.py:151 +#: awx/conf/registry.py:154 msgid "This value has been set manually in a settings file." msgstr "この値は設定ファイルに手動で設定されました。" @@ -1265,93 +1304,91 @@ msgstr "設定の詳細" msgid "Logging Connectivity Test" msgstr "ロギング接続テスト" -#: awx/main/access.py:224 +#: awx/main/access.py:44 +msgid "Resource is being used by running jobs." +msgstr "リソースが実行中のジョブで使用されています。" + +#: awx/main/access.py:237 #, python-format msgid "Bad data found in related field %s." msgstr "関連フィールド %s に不正データが見つかりました。" -#: awx/main/access.py:268 +#: awx/main/access.py:281 msgid "License is missing." msgstr "ライセンスが見つかりません。" -#: awx/main/access.py:270 +#: awx/main/access.py:283 msgid "License has expired." msgstr "ライセンスの有効期限が切れました。" -#: awx/main/access.py:278 +#: awx/main/access.py:291 #, python-format msgid "License count of %s instances has been reached." msgstr "%s インスタンスのライセンス数に達しました。" -#: awx/main/access.py:280 +#: awx/main/access.py:293 #, python-format msgid "License count of %s instances has been exceeded." msgstr "%s インスタンスのライセンス数を超えました。" -#: awx/main/access.py:282 +#: awx/main/access.py:295 msgid "Host count exceeds available instances." msgstr "ホスト数が利用可能なインスタンスの上限を上回っています。" -#: awx/main/access.py:286 +#: awx/main/access.py:299 #, python-format msgid "Feature %s is not enabled in the active license." msgstr "機能 %s はアクティブなライセンスで有効にされていません。" -#: awx/main/access.py:288 +#: awx/main/access.py:301 msgid "Features not found in active license." msgstr "各種機能はアクティブなライセンスにありません。" -#: awx/main/access.py:534 awx/main/access.py:619 awx/main/access.py:748 -#: awx/main/access.py:801 awx/main/access.py:1063 awx/main/access.py:1263 -#: awx/main/access.py:1725 -msgid "Resource is being used by running jobs" -msgstr "リソースが実行中のジョブで使用されています" - -#: awx/main/access.py:675 +#: awx/main/access.py:707 msgid "Unable to change inventory on a host." msgstr "ホストのインベントリーを変更できません。" -#: awx/main/access.py:692 awx/main/access.py:737 +#: awx/main/access.py:724 awx/main/access.py:769 msgid "Cannot associate two items from different inventories." msgstr "異なるインベントリーの 2 つの項目を関連付けることはできません。" -#: awx/main/access.py:725 +#: awx/main/access.py:757 msgid "Unable to change inventory on a group." msgstr "グループのインベントリーを変更できません。" -#: awx/main/access.py:983 +#: awx/main/access.py:1017 msgid "Unable to change organization on a team." msgstr "チームの組織を変更できません。" -#: awx/main/access.py:996 +#: awx/main/access.py:1030 msgid "The {} role cannot be assigned to a team" msgstr "{} ロールをチームに割り当てることができません" -#: awx/main/access.py:998 +#: awx/main/access.py:1032 msgid "The admin_role for a User cannot be assigned to a team" msgstr "ユーザーの admin_role をチームに割り当てることができません" -#: awx/main/access.py:1443 +#: awx/main/access.py:1479 msgid "Job has been orphaned from its job template." msgstr "ジョブはジョブテンプレートから孤立しています。" -#: awx/main/access.py:1445 +#: awx/main/access.py:1481 msgid "You do not have execute permission to related job template." msgstr "関連するジョブテンプレートに対する実行パーミッションがありません。" -#: awx/main/access.py:1448 +#: awx/main/access.py:1484 msgid "Job was launched with prompted fields." msgstr "ジョブはプロンプトされたフィールドで起動されています。" -#: awx/main/access.py:1450 +#: awx/main/access.py:1486 msgid " Organization level permissions required." msgstr "組織レベルのパーミッションが必要です。" -#: awx/main/access.py:1452 +#: awx/main/access.py:1488 msgid " You do not have permission to related resources." msgstr "関連リソースに対するパーミッションがありません。" -#: awx/main/access.py:1798 +#: awx/main/access.py:1833 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." @@ -1501,7 +1538,7 @@ msgstr "" #: awx/main/conf.py:159 msgid "Paths to hide from isolated jobs" -msgstr "分離されたジョブから非表示にするパス" +msgstr "分離されたジョブの非表示にするパス" #: awx/main/conf.py:160 msgid "" @@ -1510,7 +1547,7 @@ msgstr "分離されたプロセスから非表示にする追加パスです。 #: awx/main/conf.py:169 msgid "Paths to expose to isolated jobs" -msgstr "分離されたジョブに公開するパス" +msgstr "分離されたジョブの公開するパス" #: awx/main/conf.py:170 msgid "" @@ -1831,99 +1868,99 @@ msgstr "" msgid "\n" msgstr "\n" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Sudo" msgstr "Sudo" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Su" msgstr "Su" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pbrun" msgstr "Pbrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pfexec" msgstr "Pfexec" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "DZDO" msgstr "DZDO" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pmrun" msgstr "Pmrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Runas" msgstr "Runas" -#: awx/main/fields.py:56 +#: awx/main/fields.py:57 #, python-format msgid "'%s' is not one of ['%s']" msgstr "'%s' は ['%s'] のいずれでもありません。" -#: awx/main/fields.py:531 +#: awx/main/fields.py:533 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "\"%s\" が設定されていない場合は設定できません" -#: awx/main/fields.py:547 +#: awx/main/fields.py:549 #, python-format msgid "required for %s" msgstr "%s に必須です" -#: awx/main/fields.py:571 +#: awx/main/fields.py:573 msgid "must be set when SSH key is encrypted." msgstr "SSH キーが暗号化されている場合に設定する必要があります。" -#: awx/main/fields.py:577 +#: awx/main/fields.py:579 msgid "should not be set when SSH key is not encrypted." msgstr "SSH キーが暗号化されていない場合は設定できません。" -#: awx/main/fields.py:635 +#: awx/main/fields.py:637 msgid "'dependencies' is not supported for custom credentials." msgstr "「dependencies (依存関係)」はカスタム認証情報の場合にはサポートされません。" -#: awx/main/fields.py:649 +#: awx/main/fields.py:651 msgid "\"tower\" is a reserved field name" msgstr "「tower」は予約されたフィールド名です" -#: awx/main/fields.py:656 +#: awx/main/fields.py:658 #, python-format msgid "field IDs must be unique (%s)" msgstr "フィールド ID は固有でなければなりません (%s)" -#: awx/main/fields.py:669 +#: awx/main/fields.py:671 #, python-format msgid "%s not allowed for %s type (%s)" msgstr "%s は %s タイプに許可されません (%s)" -#: awx/main/fields.py:753 +#: awx/main/fields.py:755 #, python-format msgid "%s uses an undefined field (%s)" msgstr "%s は未定義のフィールドを使用します (%s)" -#: awx/main/middleware.py:121 +#: awx/main/middleware.py:157 msgid "Formats of all available named urls" msgstr "利用可能なすべての名前付き url の形式" -#: awx/main/middleware.py:122 +#: awx/main/middleware.py:158 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." msgstr "名前付き URL を持つ利用可能なすべての標準形式を表示するキーと値のペアの読み取り専用リストです。" -#: awx/main/middleware.py:124 awx/main/middleware.py:134 +#: awx/main/middleware.py:160 awx/main/middleware.py:170 msgid "Named URL" msgstr "名前付き URL" -#: awx/main/middleware.py:131 +#: awx/main/middleware.py:167 msgid "List of all named url graph nodes." msgstr "すべての名前付き URL グラフノードの一覧です。" -#: awx/main/middleware.py:132 +#: awx/main/middleware.py:168 msgid "" "Read-only list of key-value pairs that exposes named URL graph topology. Use" " this list to programmatically generate named URLs for resources" @@ -1931,51 +1968,51 @@ msgstr "" "名前付き URL グラフトポロジーを公開するキーと値のペアの読み取り専用一覧です。この一覧を使用してリソースの名前付き URL " "をプログラムで生成します。" -#: awx/main/migrations/_reencrypt.py:23 awx/main/models/notifications.py:33 +#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:33 msgid "Email" msgstr "メール" -#: awx/main/migrations/_reencrypt.py:24 awx/main/models/notifications.py:34 +#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:34 msgid "Slack" msgstr "Slack" -#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:35 +#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:35 msgid "Twilio" msgstr "Twilio" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:36 +#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:36 msgid "Pagerduty" msgstr "Pagerduty" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:37 +#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:37 msgid "HipChat" msgstr "HipChat" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:38 +#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:38 msgid "Webhook" msgstr "Webhook" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:39 +#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:39 msgid "IRC" msgstr "IRC" -#: awx/main/models/activity_stream.py:24 +#: awx/main/models/activity_stream.py:25 msgid "Entity Created" msgstr "エンティティーの作成" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:26 msgid "Entity Updated" msgstr "エンティティーの更新" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:27 msgid "Entity Deleted" msgstr "エンティティーの削除" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:28 msgid "Entity Associated with another Entity" msgstr "エンティティーの別のエンティティーへの関連付け" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:29 msgid "Entity was Disassociated with another Entity" msgstr "エンティティーの別のエンティティーとの関連付けの解除" @@ -2001,43 +2038,43 @@ msgstr "アドホックコマンドのサポートされていないモジュー msgid "No argument passed to %s module." msgstr "%s モジュールに渡される引数はありません。" -#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:904 +#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:911 msgid "Host Failed" msgstr "ホストの失敗" -#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:905 +#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:912 msgid "Host OK" msgstr "ホスト OK" -#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:908 +#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:915 msgid "Host Unreachable" msgstr "ホストに到達できません" -#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:907 +#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:914 msgid "Host Skipped" msgstr "ホストがスキップされました" -#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:935 +#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:942 msgid "Debug" msgstr "デバッグ" -#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:936 +#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:943 msgid "Verbose" msgstr "詳細" -#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:937 +#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:944 msgid "Deprecated" msgstr "非推奨" -#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:938 +#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:945 msgid "Warning" msgstr "警告" -#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:939 +#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:946 msgid "System Warning" msgstr "システム警告" -#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:940 +#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:947 #: awx/main/models/unified_jobs.py:64 msgid "Error" msgstr "エラー" @@ -2056,127 +2093,127 @@ msgstr "チェック" msgid "Scan" msgstr "スキャン" -#: awx/main/models/credential.py:82 +#: awx/main/models/credential.py:86 msgid "Host" msgstr "ホスト" -#: awx/main/models/credential.py:83 +#: awx/main/models/credential.py:87 msgid "The hostname or IP address to use." msgstr "使用するホスト名または IP アドレス。" -#: awx/main/models/credential.py:89 +#: awx/main/models/credential.py:93 msgid "Username" msgstr "ユーザー名" -#: awx/main/models/credential.py:90 +#: awx/main/models/credential.py:94 msgid "Username for this credential." msgstr "この認証情報のユーザー名。" -#: awx/main/models/credential.py:96 +#: awx/main/models/credential.py:100 msgid "Password" msgstr "パスワード" -#: awx/main/models/credential.py:97 +#: awx/main/models/credential.py:101 msgid "" "Password for this credential (or \"ASK\" to prompt the user for machine " "credentials)." msgstr "この認証情報のパスワード (またはマシンの認証情報を求めるプロンプトを出すには 「ASK」)。" -#: awx/main/models/credential.py:104 +#: awx/main/models/credential.py:108 msgid "Security Token" msgstr "セキュリティートークン" -#: awx/main/models/credential.py:105 +#: awx/main/models/credential.py:109 msgid "Security Token for this credential" msgstr "この認証情報のセキュリティートークン" -#: awx/main/models/credential.py:111 +#: awx/main/models/credential.py:115 msgid "Project" msgstr "プロジェクト" -#: awx/main/models/credential.py:112 +#: awx/main/models/credential.py:116 msgid "The identifier for the project." msgstr "プロジェクトの識別子。" -#: awx/main/models/credential.py:118 +#: awx/main/models/credential.py:122 msgid "Domain" msgstr "ドメイン" -#: awx/main/models/credential.py:119 +#: awx/main/models/credential.py:123 msgid "The identifier for the domain." msgstr "ドメインの識別子。" -#: awx/main/models/credential.py:124 +#: awx/main/models/credential.py:128 msgid "SSH private key" msgstr "SSH 秘密鍵" -#: awx/main/models/credential.py:125 +#: awx/main/models/credential.py:129 msgid "RSA or DSA private key to be used instead of password." msgstr "パスワードの代わりに使用される RSA または DSA 秘密鍵。" -#: awx/main/models/credential.py:131 +#: awx/main/models/credential.py:135 msgid "SSH key unlock" msgstr "SSH キーのロック解除" -#: awx/main/models/credential.py:132 +#: awx/main/models/credential.py:136 msgid "" "Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " "user for machine credentials)." msgstr "" "暗号化されている場合は SSH 秘密鍵のロックを解除するためのパスフレーズ (またはマシンの認証情報を求めるプロンプトを出すには「ASK」)。" -#: awx/main/models/credential.py:139 +#: awx/main/models/credential.py:143 msgid "None" msgstr "なし" -#: awx/main/models/credential.py:140 +#: awx/main/models/credential.py:144 msgid "Privilege escalation method." msgstr "権限昇格メソッド。" -#: awx/main/models/credential.py:146 +#: awx/main/models/credential.py:150 msgid "Privilege escalation username." msgstr "権限昇格ユーザー名。" -#: awx/main/models/credential.py:152 +#: awx/main/models/credential.py:156 msgid "Password for privilege escalation method." msgstr "権限昇格メソッドのパスワード。" -#: awx/main/models/credential.py:158 +#: awx/main/models/credential.py:162 msgid "Vault password (or \"ASK\" to prompt the user)." msgstr "Vault パスワード (またはユーザーにプロンプトを出すには「ASK」)。" -#: awx/main/models/credential.py:162 +#: awx/main/models/credential.py:166 msgid "Whether to use the authorize mechanism." msgstr "認証メカニズムを使用するかどうか。" -#: awx/main/models/credential.py:168 +#: awx/main/models/credential.py:172 msgid "Password used by the authorize mechanism." msgstr "認証メカニズムで使用されるパスワード。" -#: awx/main/models/credential.py:174 +#: awx/main/models/credential.py:178 msgid "Client Id or Application Id for the credential" msgstr "認証情報のクライアント ID またはアプリケーション ID" -#: awx/main/models/credential.py:180 +#: awx/main/models/credential.py:184 msgid "Secret Token for this credential" msgstr "この認証情報のシークレットトークン" -#: awx/main/models/credential.py:186 +#: awx/main/models/credential.py:190 msgid "Subscription identifier for this credential" msgstr "この認証情報のサブスクリプション識別子" -#: awx/main/models/credential.py:192 +#: awx/main/models/credential.py:196 msgid "Tenant identifier for this credential" msgstr "この認証情報のテナント識別子" -#: awx/main/models/credential.py:216 +#: awx/main/models/credential.py:220 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." msgstr "" "作成する必要のある証明書のタイプを指定します。それぞれのタイプの詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/main/models/credential.py:230 awx/main/models/credential.py:416 +#: awx/main/models/credential.py:234 awx/main/models/credential.py:420 msgid "" "Enter inputs using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2185,31 +2222,31 @@ msgstr "" "JSON または YAML 構文のいずれかを使用して入力を行います。ラジオボタンを使用してこれらの間で切り替えを行います。構文のサンプルについては " "Ansible Tower ドキュメントを参照してください。" -#: awx/main/models/credential.py:397 +#: awx/main/models/credential.py:401 msgid "Machine" msgstr "マシン" -#: awx/main/models/credential.py:398 +#: awx/main/models/credential.py:402 msgid "Vault" msgstr "Vault" -#: awx/main/models/credential.py:399 +#: awx/main/models/credential.py:403 msgid "Network" msgstr "ネットワーク" -#: awx/main/models/credential.py:400 +#: awx/main/models/credential.py:404 msgid "Source Control" msgstr "ソースコントロール" -#: awx/main/models/credential.py:401 +#: awx/main/models/credential.py:405 msgid "Cloud" msgstr "クラウド" -#: awx/main/models/credential.py:402 +#: awx/main/models/credential.py:406 msgid "Insights" msgstr "Insights" -#: awx/main/models/credential.py:423 +#: awx/main/models/credential.py:427 msgid "" "Enter injectors using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2219,6 +2256,11 @@ msgstr "" "構文のいずれかを使用してインジェクターを入力します。ラジオボタンを使用してこれらの間で切り替えを行います。構文のサンプルについては Ansible " "Tower ドキュメントを参照してください。" +#: awx/main/models/credential.py:478 +#, python-format +msgid "adding %s credential type" +msgstr "%s 認証情報タイプの追加" + #: awx/main/models/fact.py:25 msgid "Host for the facts that the fact scan captured." msgstr "ファクトスキャンがキャプチャーしたファクトのホスト。" @@ -2233,11 +2275,11 @@ msgid "" "host." msgstr "単一ホストのタイムスタンプでキャプチャーされるモジュールファクトの任意の JSON 構造。" -#: awx/main/models/ha.py:76 +#: awx/main/models/ha.py:78 msgid "Instances that are members of this InstanceGroup" msgstr "このインスタンスグループのメンバーであるインスタンス" -#: awx/main/models/ha.py:81 +#: awx/main/models/ha.py:83 msgid "Instance Group to remotely control this group." msgstr "このグループをリモートで制御するためのインスタンスグループ" @@ -2420,151 +2462,161 @@ msgid "Google Compute Engine" msgstr "Google Compute Engine" #: awx/main/models/inventory.py:870 -msgid "Microsoft Azure Classic (deprecated)" -msgstr "Microsoft Azure Classic (非推奨)" - -#: awx/main/models/inventory.py:871 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/inventory.py:872 +#: awx/main/models/inventory.py:871 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/inventory.py:873 +#: awx/main/models/inventory.py:872 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/inventory.py:874 +#: awx/main/models/inventory.py:873 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/inventory.py:875 +#: awx/main/models/inventory.py:874 msgid "OpenStack" msgstr "OpenStack" +#: awx/main/models/inventory.py:875 +msgid "oVirt4" +msgstr "oVirt4" + #: awx/main/models/inventory.py:876 +msgid "Ansible Tower" +msgstr "Ansible Tower" + +#: awx/main/models/inventory.py:877 msgid "Custom Script" msgstr "カスタムスクリプト" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:994 msgid "Inventory source variables in YAML or JSON format." msgstr "YAML または JSON 形式のインベントリーソース変数。" -#: awx/main/models/inventory.py:1012 +#: awx/main/models/inventory.py:1013 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." msgstr "カンマ区切りのフィルター式の一覧 (EC2 のみ) です。ホストは、フィルターのいずれかが一致する場合にインポートされます。" -#: awx/main/models/inventory.py:1018 +#: awx/main/models/inventory.py:1019 msgid "Limit groups automatically created from inventory source (EC2 only)." msgstr "インベントリーソースから自動的に作成されるグループを制限します (EC2 のみ)。" -#: awx/main/models/inventory.py:1022 +#: awx/main/models/inventory.py:1023 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "リモートインベントリーソースからのローカルグループおよびホストを上書きします。" -#: awx/main/models/inventory.py:1026 +#: awx/main/models/inventory.py:1027 msgid "Overwrite local variables from remote inventory source." msgstr "リモートインベントリーソースからのローカル変数を上書きします。" -#: awx/main/models/inventory.py:1031 awx/main/models/jobs.py:159 +#: awx/main/models/inventory.py:1032 awx/main/models/jobs.py:160 #: awx/main/models/projects.py:117 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "タスクが取り消される前の実行時間 (秒数)。" -#: awx/main/models/inventory.py:1064 +#: awx/main/models/inventory.py:1065 msgid "Image ID" msgstr "イメージ ID" -#: awx/main/models/inventory.py:1065 +#: awx/main/models/inventory.py:1066 msgid "Availability Zone" msgstr "アベイラビリティーゾーン" -#: awx/main/models/inventory.py:1066 +#: awx/main/models/inventory.py:1067 msgid "Account" msgstr "アカウント" -#: awx/main/models/inventory.py:1067 +#: awx/main/models/inventory.py:1068 msgid "Instance ID" msgstr "インスタンス ID" -#: awx/main/models/inventory.py:1068 +#: awx/main/models/inventory.py:1069 msgid "Instance State" msgstr "インスタンスの状態" -#: awx/main/models/inventory.py:1069 +#: awx/main/models/inventory.py:1070 msgid "Instance Type" msgstr "インスタンスタイプ" -#: awx/main/models/inventory.py:1070 +#: awx/main/models/inventory.py:1071 msgid "Key Name" msgstr "キー名" -#: awx/main/models/inventory.py:1071 +#: awx/main/models/inventory.py:1072 msgid "Region" msgstr "リージョン" -#: awx/main/models/inventory.py:1072 +#: awx/main/models/inventory.py:1073 msgid "Security Group" msgstr "セキュリティーグループ" -#: awx/main/models/inventory.py:1073 +#: awx/main/models/inventory.py:1074 msgid "Tags" msgstr "タグ" -#: awx/main/models/inventory.py:1074 +#: awx/main/models/inventory.py:1075 msgid "Tag None" msgstr "タグ None" -#: awx/main/models/inventory.py:1075 +#: awx/main/models/inventory.py:1076 msgid "VPC ID" msgstr "VPC ID" -#: awx/main/models/inventory.py:1138 +#: awx/main/models/inventory.py:1145 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." msgstr "クラウドベースのインベントリーソース (%s など) には一致するクラウドサービスの認証情報が必要です。" -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:1152 msgid "Credential is required for a cloud source." msgstr "認証情報がクラウドソースに必要です。" -#: awx/main/models/inventory.py:1167 +#: awx/main/models/inventory.py:1155 +msgid "" +"Credentials of type machine, source control, insights and vault are " +"disallowed for custom inventory sources." +msgstr "タイプがマシン、ソースコントロール、Insights および Vault の認証情報はカスタムインベントリーソースには許可されません。" + +#: awx/main/models/inventory.py:1179 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "無効な %(source)s リージョン: %(region)s" -#: awx/main/models/inventory.py:1191 +#: awx/main/models/inventory.py:1203 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "無効なフィルター式: %(filter)s" -#: awx/main/models/inventory.py:1212 +#: awx/main/models/inventory.py:1224 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "無効なグループ (選択による): %(choice)s" -#: awx/main/models/inventory.py:1247 +#: awx/main/models/inventory.py:1259 msgid "Project containing inventory file used as source." msgstr "ソースとして使用されるインベントリーファイルが含まれるプロジェクト。" -#: awx/main/models/inventory.py:1395 +#: awx/main/models/inventory.py:1407 #, python-format msgid "" "Unable to configure this item for cloud sync. It is already managed by %s." msgstr "クラウド同期用にこの項目を設定できません。すでに %s によって管理されています。" -#: awx/main/models/inventory.py:1405 +#: awx/main/models/inventory.py:1417 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." msgstr "複数の SCM ベースのインベントリーソースについて、インベントリー別のプロジェクト更新時の更新は許可されません。" -#: awx/main/models/inventory.py:1412 +#: awx/main/models/inventory.py:1424 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " @@ -2573,34 +2625,34 @@ msgstr "" "プロジェクト更新時の更新に設定している場合、SCM " "ベースのインベントリーソースを更新できません。その代わりに起動時に更新するように対応するソースプロジェクトを設定します。" -#: awx/main/models/inventory.py:1418 +#: awx/main/models/inventory.py:1430 msgid "SCM type sources must set `overwrite_vars` to `true`." msgstr "SCM タイプソースは「overwrite_vars」を「true」に設定する必要があります。" -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1435 msgid "Cannot set source_path if not SCM type." msgstr "SCM タイプでない場合 source_path を設定できません。" -#: awx/main/models/inventory.py:1448 +#: awx/main/models/inventory.py:1460 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "このプロジェクト更新のインベントリーファイルがインベントリー更新に使用されました。" -#: awx/main/models/inventory.py:1561 +#: awx/main/models/inventory.py:1573 msgid "Inventory script contents" msgstr "インベントリースクリプトの内容" -#: awx/main/models/inventory.py:1566 +#: awx/main/models/inventory.py:1578 msgid "Organization owning this inventory script" msgstr "このインベントリースクリプトを所有する組織" -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:66 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" msgstr "有効にされている場合、ホストのテンプレート化されたファイルに追加されるテキストの変更が標準出力に表示されます。" -#: awx/main/models/jobs.py:163 +#: awx/main/models/jobs.py:164 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" " at the end of a playbook run to the database and caching facts for use by " @@ -2609,137 +2661,137 @@ msgstr "" "有効にされている場合、 Tower は Ansible ファクトキャッシュプラグインとして機能します。データベースに対する Playbook " "実行の終了時にファクトが保持され、 Ansible で使用できるようにキャッシュされます。" -#: awx/main/models/jobs.py:172 +#: awx/main/models/jobs.py:173 msgid "You must provide an SSH credential." msgstr "SSH 認証情報を指定する必要があります。" -#: awx/main/models/jobs.py:180 +#: awx/main/models/jobs.py:181 msgid "You must provide a Vault credential." msgstr "Vault 認証情報を指定する必要があります。" -#: awx/main/models/jobs.py:316 +#: awx/main/models/jobs.py:317 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "ジョブテンプレートは「inventory」を指定するか、このプロンプトを許可する必要があります。" -#: awx/main/models/jobs.py:320 +#: awx/main/models/jobs.py:321 msgid "Job Template must provide 'credential' or allow prompting for it." msgstr "ジョブテンプレートは「credential」を指定するか、このプロンプトを許可する必要があります。" -#: awx/main/models/jobs.py:422 +#: awx/main/models/jobs.py:427 msgid "Cannot override job_type to or from a scan job." msgstr "スキャンジョブから/への job_type を上書きを実行できません。" -#: awx/main/models/jobs.py:488 awx/main/models/projects.py:263 +#: awx/main/models/jobs.py:493 awx/main/models/projects.py:263 msgid "SCM Revision" msgstr "SCM リビジョン" -#: awx/main/models/jobs.py:489 +#: awx/main/models/jobs.py:494 msgid "The SCM Revision from the Project used for this job, if available" msgstr "このジョブに使用されるプロジェクトからの SCM リビジョン (ある場合)" -#: awx/main/models/jobs.py:497 +#: awx/main/models/jobs.py:502 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" msgstr "SCM 更新タスクは、Playbook がジョブの実行で利用可能であったことを確認するために使用されます" -#: awx/main/models/jobs.py:802 +#: awx/main/models/jobs.py:809 msgid "job host summaries" msgstr "ジョブホストの概要" -#: awx/main/models/jobs.py:906 +#: awx/main/models/jobs.py:913 msgid "Host Failure" msgstr "ホストの失敗" -#: awx/main/models/jobs.py:909 awx/main/models/jobs.py:923 +#: awx/main/models/jobs.py:916 awx/main/models/jobs.py:930 msgid "No Hosts Remaining" msgstr "残りのホストがありません" -#: awx/main/models/jobs.py:910 +#: awx/main/models/jobs.py:917 msgid "Host Polling" msgstr "ホストのポーリング" -#: awx/main/models/jobs.py:911 +#: awx/main/models/jobs.py:918 msgid "Host Async OK" msgstr "ホストの非同期 OK" -#: awx/main/models/jobs.py:912 +#: awx/main/models/jobs.py:919 msgid "Host Async Failure" msgstr "ホストの非同期失敗" -#: awx/main/models/jobs.py:913 +#: awx/main/models/jobs.py:920 msgid "Item OK" msgstr "項目 OK" -#: awx/main/models/jobs.py:914 +#: awx/main/models/jobs.py:921 msgid "Item Failed" msgstr "項目の失敗" -#: awx/main/models/jobs.py:915 +#: awx/main/models/jobs.py:922 msgid "Item Skipped" msgstr "項目のスキップ" -#: awx/main/models/jobs.py:916 +#: awx/main/models/jobs.py:923 msgid "Host Retry" msgstr "ホストの再試行" -#: awx/main/models/jobs.py:918 +#: awx/main/models/jobs.py:925 msgid "File Difference" msgstr "ファイルの相違点" -#: awx/main/models/jobs.py:919 +#: awx/main/models/jobs.py:926 msgid "Playbook Started" msgstr "Playbook の開始" -#: awx/main/models/jobs.py:920 +#: awx/main/models/jobs.py:927 msgid "Running Handlers" msgstr "実行中のハンドラー" -#: awx/main/models/jobs.py:921 +#: awx/main/models/jobs.py:928 msgid "Including File" msgstr "組み込みファイル" -#: awx/main/models/jobs.py:922 +#: awx/main/models/jobs.py:929 msgid "No Hosts Matched" msgstr "一致するホストがありません" -#: awx/main/models/jobs.py:924 +#: awx/main/models/jobs.py:931 msgid "Task Started" msgstr "タスクの開始" -#: awx/main/models/jobs.py:926 +#: awx/main/models/jobs.py:933 msgid "Variables Prompted" msgstr "変数のプロモート" -#: awx/main/models/jobs.py:927 +#: awx/main/models/jobs.py:934 msgid "Gathering Facts" msgstr "ファクトの収集" -#: awx/main/models/jobs.py:928 +#: awx/main/models/jobs.py:935 msgid "internal: on Import for Host" msgstr "内部: ホストのインポート時" -#: awx/main/models/jobs.py:929 +#: awx/main/models/jobs.py:936 msgid "internal: on Not Import for Host" msgstr "内部: ホストの非インポート時" -#: awx/main/models/jobs.py:930 +#: awx/main/models/jobs.py:937 msgid "Play Started" msgstr "プレイの開始" -#: awx/main/models/jobs.py:931 +#: awx/main/models/jobs.py:938 msgid "Playbook Complete" msgstr "Playbook の完了" -#: awx/main/models/jobs.py:1363 +#: awx/main/models/jobs.py:1351 msgid "Remove jobs older than a certain number of days" msgstr "特定の日数より前のジョブを削除" -#: awx/main/models/jobs.py:1364 +#: awx/main/models/jobs.py:1352 msgid "Remove activity stream entries older than a certain number of days" msgstr "特定の日数より前のアクティビティーストリームのエントリーを削除" -#: awx/main/models/jobs.py:1365 +#: awx/main/models/jobs.py:1353 msgid "Purge and/or reduce the granularity of system tracking data" msgstr "システムトラッキングデータの詳細度の削除/削減" @@ -3029,11 +3081,11 @@ msgstr "実行中" #: awx/main/models/unified_jobs.py:65 msgid "Canceled" -msgstr "取り消されました" +msgstr "取り消し" #: awx/main/models/unified_jobs.py:69 msgid "Never Updated" -msgstr "更新されていません" +msgstr "未更新" #: awx/main/models/unified_jobs.py:73 awx/ui/templates/ui/index.html:67 #: awx/ui/templates/ui/index.html.py:86 @@ -3142,13 +3194,13 @@ msgstr "Twilio への接続時の例外: {}" msgid "Error sending notification webhook: {}" msgstr "通知 webhook の送信時のエラー: {}" -#: awx/main/scheduler/__init__.py:184 +#: awx/main/scheduler/task_manager.py:197 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" msgstr "ワークフローから起動されるジョブは、正常な状態にないか、または手動の認証が必要であるために開始できませんでした" -#: awx/main/scheduler/__init__.py:188 +#: awx/main/scheduler/task_manager.py:201 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" @@ -3166,7 +3218,7 @@ msgstr "Ansible Tower ライセンスがまもなく期限切れになります" msgid "status_str must be either succeeded or failed" msgstr "status_str は成功または失敗のいずれかである必要があります" -#: awx/main/tasks.py:1531 +#: awx/main/tasks.py:1549 msgid "Dependent inventory update {} was canceled." msgstr "依存するインベントリーの更新 {} が取り消されました。" @@ -3175,38 +3227,38 @@ msgstr "依存するインベントリーの更新 {} が取り消されまし msgid "Unable to convert \"%s\" to boolean" msgstr "\"%s\" をブール値に変換できません" -#: awx/main/utils/common.py:209 +#: awx/main/utils/common.py:235 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "サポートされない SCM タイプ \"%s\"" -#: awx/main/utils/common.py:216 awx/main/utils/common.py:228 -#: awx/main/utils/common.py:247 +#: awx/main/utils/common.py:242 awx/main/utils/common.py:254 +#: awx/main/utils/common.py:273 #, python-format msgid "Invalid %s URL" msgstr "無効な %s URL" -#: awx/main/utils/common.py:218 awx/main/utils/common.py:257 +#: awx/main/utils/common.py:244 awx/main/utils/common.py:283 #, python-format msgid "Unsupported %s URL" msgstr "サポートされていない %s URL" -#: awx/main/utils/common.py:259 +#: awx/main/utils/common.py:285 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "ファイル:// URL のサポートされていないホスト \"%s\" " -#: awx/main/utils/common.py:261 +#: awx/main/utils/common.py:287 #, python-format msgid "Host is required for %s URL" msgstr "%s URL にはホストが必要です" -#: awx/main/utils/common.py:279 +#: awx/main/utils/common.py:305 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "%s への SSH アクセスではユーザー名を \"git\" にする必要があります。" -#: awx/main/utils/common.py:285 +#: awx/main/utils/common.py:311 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "%s への SSH アクセスではユーザー名を \"hg\" にする必要があります。" @@ -3319,287 +3371,287 @@ msgstr "サーバーエラー" msgid "A server error has occurred." msgstr "サーバーエラーが発生しました。" -#: awx/settings/defaults.py:664 +#: awx/settings/defaults.py:665 msgid "US East (Northern Virginia)" msgstr "米国東部 (バージニア北部)" -#: awx/settings/defaults.py:665 +#: awx/settings/defaults.py:666 msgid "US East (Ohio)" msgstr "米国東部 (オハイオ)" -#: awx/settings/defaults.py:666 +#: awx/settings/defaults.py:667 msgid "US West (Oregon)" msgstr "米国西部 (オレゴン)" -#: awx/settings/defaults.py:667 +#: awx/settings/defaults.py:668 msgid "US West (Northern California)" msgstr "米国西部 (北カリフォルニア)" -#: awx/settings/defaults.py:668 +#: awx/settings/defaults.py:669 msgid "Canada (Central)" msgstr "カナダ (中部)" -#: awx/settings/defaults.py:669 +#: awx/settings/defaults.py:670 msgid "EU (Frankfurt)" msgstr "EU (フランクフルト)" -#: awx/settings/defaults.py:670 +#: awx/settings/defaults.py:671 msgid "EU (Ireland)" msgstr "EU (アイルランド)" -#: awx/settings/defaults.py:671 +#: awx/settings/defaults.py:672 msgid "EU (London)" msgstr "EU (ロンドン)" -#: awx/settings/defaults.py:672 +#: awx/settings/defaults.py:673 msgid "Asia Pacific (Singapore)" msgstr "アジア太平洋 (シンガポール)" -#: awx/settings/defaults.py:673 +#: awx/settings/defaults.py:674 msgid "Asia Pacific (Sydney)" msgstr "アジア太平洋 (シドニー)" -#: awx/settings/defaults.py:674 +#: awx/settings/defaults.py:675 msgid "Asia Pacific (Tokyo)" msgstr "アジア太平洋 (東京)" -#: awx/settings/defaults.py:675 +#: awx/settings/defaults.py:676 msgid "Asia Pacific (Seoul)" msgstr "アジア太平洋 (ソウル)" -#: awx/settings/defaults.py:676 +#: awx/settings/defaults.py:677 msgid "Asia Pacific (Mumbai)" msgstr "アジア太平洋 (ムンバイ)" -#: awx/settings/defaults.py:677 +#: awx/settings/defaults.py:678 msgid "South America (Sao Paulo)" msgstr "南アメリカ (サンパウロ)" -#: awx/settings/defaults.py:678 +#: awx/settings/defaults.py:679 msgid "US West (GovCloud)" msgstr "米国西部 (GovCloud)" -#: awx/settings/defaults.py:679 +#: awx/settings/defaults.py:680 msgid "China (Beijing)" msgstr "中国 (北京)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:729 msgid "US East 1 (B)" msgstr "米国東部 1 (B)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:730 msgid "US East 1 (C)" msgstr "米国東部 1 (C)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:731 msgid "US East 1 (D)" msgstr "米国東部 1 (D)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:732 msgid "US East 4 (A)" msgstr "米国東部 4 (A)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:733 msgid "US East 4 (B)" msgstr "米国東部 4 (B)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:734 msgid "US East 4 (C)" msgstr "米国東部 4 (C)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:735 msgid "US Central (A)" msgstr "米国中部 (A)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:736 msgid "US Central (B)" msgstr "米国中部 (B)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:737 msgid "US Central (C)" msgstr "米国中部 (C)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:738 msgid "US Central (F)" msgstr "米国中部 (F)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:739 msgid "US West (A)" msgstr "米国西部 (A)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:740 msgid "US West (B)" msgstr "米国西部 (B)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:741 msgid "US West (C)" msgstr "米国西部 (C)" -#: awx/settings/defaults.py:741 +#: awx/settings/defaults.py:742 msgid "Europe West 1 (B)" msgstr "欧州西部 1 (B)" -#: awx/settings/defaults.py:742 +#: awx/settings/defaults.py:743 msgid "Europe West 1 (C)" msgstr "欧州西部 1 (C)" -#: awx/settings/defaults.py:743 +#: awx/settings/defaults.py:744 msgid "Europe West 1 (D)" msgstr "欧州西部 1 (D)" -#: awx/settings/defaults.py:744 +#: awx/settings/defaults.py:745 msgid "Europe West 2 (A)" msgstr "欧州西部 2 (A)" -#: awx/settings/defaults.py:745 +#: awx/settings/defaults.py:746 msgid "Europe West 2 (B)" msgstr "欧州西部 2 (B)" -#: awx/settings/defaults.py:746 +#: awx/settings/defaults.py:747 msgid "Europe West 2 (C)" msgstr "欧州西部 2 (C)" -#: awx/settings/defaults.py:747 +#: awx/settings/defaults.py:748 msgid "Asia East (A)" msgstr "アジア東部 (A)" -#: awx/settings/defaults.py:748 +#: awx/settings/defaults.py:749 msgid "Asia East (B)" msgstr "アジア東部 (B)" -#: awx/settings/defaults.py:749 +#: awx/settings/defaults.py:750 msgid "Asia East (C)" msgstr "アジア東部 (C)" -#: awx/settings/defaults.py:750 +#: awx/settings/defaults.py:751 msgid "Asia Southeast (A)" msgstr "アジア南東部 (A)" -#: awx/settings/defaults.py:751 +#: awx/settings/defaults.py:752 msgid "Asia Southeast (B)" msgstr "アジア南東部 (B)" -#: awx/settings/defaults.py:752 +#: awx/settings/defaults.py:753 msgid "Asia Northeast (A)" msgstr "アジア北東部 (A)" -#: awx/settings/defaults.py:753 +#: awx/settings/defaults.py:754 msgid "Asia Northeast (B)" msgstr "アジア北東部 (B)" -#: awx/settings/defaults.py:754 +#: awx/settings/defaults.py:755 msgid "Asia Northeast (C)" msgstr "アジア北東部 (C)" -#: awx/settings/defaults.py:755 +#: awx/settings/defaults.py:756 msgid "Australia Southeast (A)" msgstr "オーストラリア南東部 (A)" -#: awx/settings/defaults.py:756 +#: awx/settings/defaults.py:757 msgid "Australia Southeast (B)" msgstr "オーストラリア南東部 (B)" -#: awx/settings/defaults.py:757 +#: awx/settings/defaults.py:758 msgid "Australia Southeast (C)" msgstr "オーストラリア南東部 (C)" -#: awx/settings/defaults.py:781 +#: awx/settings/defaults.py:780 msgid "US East" msgstr "米国東部" -#: awx/settings/defaults.py:782 +#: awx/settings/defaults.py:781 msgid "US East 2" msgstr "米国東部 2" -#: awx/settings/defaults.py:783 +#: awx/settings/defaults.py:782 msgid "US Central" msgstr "米国中部" -#: awx/settings/defaults.py:784 +#: awx/settings/defaults.py:783 msgid "US North Central" msgstr "米国中北部" -#: awx/settings/defaults.py:785 +#: awx/settings/defaults.py:784 msgid "US South Central" msgstr "米国中南部" -#: awx/settings/defaults.py:786 +#: awx/settings/defaults.py:785 msgid "US West Central" msgstr "米国中西部" -#: awx/settings/defaults.py:787 +#: awx/settings/defaults.py:786 msgid "US West" msgstr "米国西部" -#: awx/settings/defaults.py:788 +#: awx/settings/defaults.py:787 msgid "US West 2" msgstr "米国西部 2" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:788 msgid "Canada East" msgstr "カナダ東部" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:789 msgid "Canada Central" msgstr "カナダ中部" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:790 msgid "Brazil South" msgstr "ブラジル南部" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:791 msgid "Europe North" msgstr "欧州北部" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:792 msgid "Europe West" msgstr "欧州西部" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:793 msgid "UK West" msgstr "英国西部" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:794 msgid "UK South" msgstr "英国南部" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:795 msgid "Asia East" msgstr "アジア東部" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:796 msgid "Asia Southeast" msgstr "アジア南東部" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:797 msgid "Australia East" msgstr "オーストラリア東部" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:798 msgid "Australia Southeast" msgstr "オーストラリア南東部 " -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:799 msgid "India West" msgstr "インド西部" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:800 msgid "India South" msgstr "インド南部" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:801 msgid "Japan East" msgstr "日本東部" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:802 msgid "Japan West" msgstr "日本西部" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:803 msgid "Korea Central" msgstr "韓国中部" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:804 msgid "Korea South" msgstr "韓国南部" @@ -4297,6 +4349,7 @@ msgid "SAML Team Map" msgstr "SAML チームマップ" #: awx/sso/fields.py:123 +#, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "無効な接続オプション: {invalid_options}" @@ -4313,14 +4366,17 @@ msgid "Subtree" msgstr "サブツリー" #: awx/sso/fields.py:214 +#, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "3 つの項目の一覧が予期されましが、{length} が取得されました。" #: awx/sso/fields.py:215 +#, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "LDAPSearch のインスタンスが予期されましたが、{input_type} が取得されました。" #: awx/sso/fields.py:251 +#, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." @@ -4328,64 +4384,79 @@ msgstr "" "LDAPSearch または LDAPSearchUnion のインスタンスが予期されましたが、{input_type} が取得されました。" #: awx/sso/fields.py:289 +#, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "無効なユーザー属性: {invalid_attrs}" #: awx/sso/fields.py:306 +#, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "LDAPGroupType のインスタンスが予期されましたが、{input_type} が取得されました。" #: awx/sso/fields.py:334 +#, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "無効なユーザーフラグ: \"{invalid_flag}\"" #: awx/sso/fields.py:350 awx/sso/fields.py:517 +#, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type}" " instead." msgstr "None、True、False、文字列または文字列の一覧が予期されましたが、{input_type} が取得されました。" #: awx/sso/fields.py:386 +#, python-brace-format msgid "Missing key(s): {missing_keys}." msgstr "キーがありません: {missing_keys}" #: awx/sso/fields.py:387 +#, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "無効なキー: {invalid_keys}" #: awx/sso/fields.py:436 awx/sso/fields.py:553 +#, python-brace-format msgid "Invalid key(s) for organization map: {invalid_keys}." msgstr "組織マップの無効なキー: {invalid_keys}" #: awx/sso/fields.py:454 +#, python-brace-format msgid "Missing required key for team map: {invalid_keys}." msgstr "チームマップの必要なキーがありません: {invalid_keys}" #: awx/sso/fields.py:455 awx/sso/fields.py:572 +#, python-brace-format msgid "Invalid key(s) for team map: {invalid_keys}." msgstr "チームマップの無効なキー: {invalid_keys}" #: awx/sso/fields.py:571 +#, python-brace-format msgid "Missing required key for team map: {missing_keys}." msgstr "チームマップで必要なキーがありません: {missing_keys}" #: awx/sso/fields.py:589 +#, python-brace-format msgid "Missing required key(s) for org info record: {missing_keys}." msgstr "組織情報レコードで必要なキーがありません: {missing_keys}" #: awx/sso/fields.py:602 +#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "組織情報の無効な言語コード: {invalid_lang_codes}" #: awx/sso/fields.py:621 +#, python-brace-format msgid "Missing required key(s) for contact: {missing_keys}." msgstr "問い合わせ先の必要なキーがありません: {missing_keys}" #: awx/sso/fields.py:633 +#, python-brace-format msgid "Missing required key(s) for IdP: {missing_keys}." msgstr "IdP で必要なキーがありません: {missing_keys}" #: awx/sso/pipeline.py:24 +#, python-brace-format msgid "An account cannot be found for {0}" msgstr "{0} のアカウントが見つかりません" @@ -4477,6 +4548,7 @@ msgid "Make a PATCH request on the %(name)s resource" msgstr "%(name)s リソースでの PATCH 要求" #: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 +#: awx/ui/conf.py:63 msgid "UI" msgstr "UI" @@ -4527,6 +4599,15 @@ msgstr "" "カスタムロゴを設定するには、作成するファイルを指定します。カスタムロゴを最適化するには、背景が透明の「.png」ファイルを使用します。GIF、PNG " "および JPEG 形式がサポートされます。" +#: awx/ui/conf.py:60 +msgid "Max Job Events Retreived by UI" +msgstr "UI で検索される最大ジョブイベント" + +#: awx/ui/conf.py:61 +msgid "" +"Maximum number of job events for the UI to retreive within a single request." +msgstr "単一要求内で検索される UI についての最大ジョブイベント数。" + #: awx/ui/fields.py:29 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " diff --git a/awx/locale/nl/LC_MESSAGES/django.po b/awx/locale/nl/LC_MESSAGES/django.po index 92fca917e2..24b5ca542b 100644 --- a/awx/locale/nl/LC_MESSAGES/django.po +++ b/awx/locale/nl/LC_MESSAGES/django.po @@ -5,16 +5,16 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-08-27 19:27+0000\n" -"PO-Revision-Date: 2017-08-31 08:53+0000\n" -"Last-Translator: helena02 \n" +"POT-Creation-Date: 2017-11-30 20:23+0000\n" +"PO-Revision-Date: 2017-12-06 02:18+0000\n" +"Last-Translator: helena \n" "Language-Team: Dutch\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"X-Generator: Zanata 4.2.1\n" +"X-Generator: Zanata 4.3.2\n" #: awx/api/authentication.py:67 msgid "Invalid token header. No credentials provided." @@ -81,25 +81,30 @@ msgstr "Filteren op %s is niet toegestaan." msgid "Loops not allowed in filters, detected on field {}." msgstr "Lussen zijn niet toegestaan in filters, gedetecteerd in veld {}." -#: awx/api/filters.py:297 +#: awx/api/filters.py:171 +#, python-brace-format +msgid "Invalid {field_name} id: {field_id}" +msgstr "Ongeldig {field_name} id: {field_id}" + +#: awx/api/filters.py:302 #, python-format msgid "cannot filter on kind %s" msgstr "kan niet filteren op soort %s" -#: awx/api/filters.py:404 +#: awx/api/filters.py:409 #, python-format msgid "cannot order by field %s" msgstr "Kan niet ordenen op veld %s" -#: awx/api/generics.py:515 awx/api/generics.py:577 +#: awx/api/generics.py:550 awx/api/generics.py:612 msgid "\"id\" field must be an integer." msgstr "'Id'-veld moet een geheel getal zijn." -#: awx/api/generics.py:574 +#: awx/api/generics.py:609 msgid "\"id\" is required to disassociate" msgstr "'id' is vereist om los te koppelen" -#: awx/api/generics.py:625 +#: awx/api/generics.py:660 msgid "{} 'id' field is missing." msgstr "{} 'id'-veld ontbreekt." @@ -180,7 +185,7 @@ msgstr "Workflowtaak" msgid "Workflow Template" msgstr "Workflowsjabloon" -#: awx/api/serializers.py:697 awx/api/serializers.py:755 awx/api/views.py:4314 +#: awx/api/serializers.py:701 awx/api/serializers.py:759 awx/api/views.py:4365 #, python-format msgid "" "Standard Output too large to display (%(text_size)d bytes), only download " @@ -190,38 +195,38 @@ msgstr "" "alleen download ondersteund voor groottes van meer dan %(supported_size)d " "bytes" -#: awx/api/serializers.py:770 +#: awx/api/serializers.py:774 msgid "Write-only field used to change the password." msgstr "Een alleen-schrijven-veld is gebruikt om het wachtwoord te wijzigen." -#: awx/api/serializers.py:772 +#: awx/api/serializers.py:776 msgid "Set if the account is managed by an external service" msgstr "Instellen als de account wordt beheerd door een externe service" -#: awx/api/serializers.py:796 +#: awx/api/serializers.py:800 msgid "Password required for new User." msgstr "Wachtwoord vereist voor een nieuwe gebruiker." -#: awx/api/serializers.py:882 +#: awx/api/serializers.py:886 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "Kan %s niet wijzigen voor gebruiker die wordt beheerd met LDAP." -#: awx/api/serializers.py:1046 +#: awx/api/serializers.py:1050 msgid "Organization is missing" msgstr "Organisatie ontbreekt" -#: awx/api/serializers.py:1050 +#: awx/api/serializers.py:1054 msgid "Update options must be set to false for manual projects." msgstr "" "De update-opties moeten voor handmatige projecten worden ingesteld op " "onwaar." -#: awx/api/serializers.py:1056 +#: awx/api/serializers.py:1060 msgid "Array of playbooks available within this project." msgstr "Er is binnen dit project een draaiboekenmatrix beschikbaar." -#: awx/api/serializers.py:1075 +#: awx/api/serializers.py:1079 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." @@ -229,68 +234,72 @@ msgstr "" "Er is binnen dit project een niet-volledige matrix met " "inventarisatiebestanden en -mappen beschikbaar." -#: awx/api/serializers.py:1282 +#: awx/api/serializers.py:1201 +msgid "Smart inventories must specify host_filter" +msgstr "Smart-inventaris moet hostfilter specificeren" + +#: awx/api/serializers.py:1303 #, python-format msgid "Invalid port specification: %s" msgstr "Ongeldige poortspecificatie: %s" -#: awx/api/serializers.py:1293 +#: awx/api/serializers.py:1314 msgid "Cannot create Host for Smart Inventory" msgstr "Kan geen host aanmaken voor Smart-inventaris" -#: awx/api/serializers.py:1315 awx/api/serializers.py:3218 -#: awx/api/serializers.py:3303 awx/main/validators.py:198 +#: awx/api/serializers.py:1336 awx/api/serializers.py:3321 +#: awx/api/serializers.py:3406 awx/main/validators.py:198 msgid "Must be valid JSON or YAML." msgstr "Moet geldig JSON of YAML zijn." -#: awx/api/serializers.py:1411 +#: awx/api/serializers.py:1432 msgid "Invalid group name." msgstr "Ongeldige groepsnaam." -#: awx/api/serializers.py:1416 +#: awx/api/serializers.py:1437 msgid "Cannot create Group for Smart Inventory" msgstr "Kan geen groep aanmaken voor Smart-inventaris" -#: awx/api/serializers.py:1488 +#: awx/api/serializers.py:1509 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" "Script moet beginnen met een hashbang-reeks, bijvoorbeeld ... #!/usr/bin/env" " python" -#: awx/api/serializers.py:1534 +#: awx/api/serializers.py:1555 msgid "`{}` is a prohibited environment variable" msgstr "`{}` is niet toegestaan als omgevingsvariabele" -#: awx/api/serializers.py:1545 +#: awx/api/serializers.py:1566 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "Als 'bron' 'aangepast' is, moet 'source_script' worden geleverd." -#: awx/api/serializers.py:1551 +#: awx/api/serializers.py:1572 msgid "Must provide an inventory." msgstr "Moet een inventaris verschaffen." -#: awx/api/serializers.py:1555 +#: awx/api/serializers.py:1576 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" "Het 'source_script' behoort niet tot dezelfde categorie als de inventaris." -#: awx/api/serializers.py:1557 +#: awx/api/serializers.py:1578 msgid "'source_script' doesn't exist." msgstr "'source_script' bestaat niet." -#: awx/api/serializers.py:1581 +#: awx/api/serializers.py:1602 msgid "Automatic group relationship, will be removed in 3.3" msgstr "Automatische groepsrelatie, wordt verwijderd in 3.3" -#: awx/api/serializers.py:1658 +#: awx/api/serializers.py:1679 msgid "Cannot use manual project for SCM-based inventory." msgstr "" "Kan geen handmatig project gebruiken voor een SCM-gebaseerde inventaris." -#: awx/api/serializers.py:1664 +#: awx/api/serializers.py:1685 msgid "" "Manual inventory sources are created automatically when a group is created " "in the v1 API." @@ -298,50 +307,58 @@ msgstr "" "Handmatige inventarisbronnen worden automatisch gemaakt wanneer er een groep" " wordt gemaakt in de v1 API." -#: awx/api/serializers.py:1669 +#: awx/api/serializers.py:1690 msgid "Setting not compatible with existing schedules." msgstr "Instelling is niet compatibel met bestaande schema's." -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1695 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "Kan geen inventarisbron aanmaken voor Smart-inventaris" -#: awx/api/serializers.py:1688 +#: awx/api/serializers.py:1709 #, python-format msgid "Cannot set %s if not SCM type." msgstr "Kan %s niet instellen als het geen SCM-type is." -#: awx/api/serializers.py:1929 +#: awx/api/serializers.py:1950 msgid "Modifications not allowed for managed credential types" msgstr "Wijzigingen zijn niet toegestaan voor beheerde referentietypen" -#: awx/api/serializers.py:1934 +#: awx/api/serializers.py:1955 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "" "Wijzigingen in inputs zijn niet toegestaan voor referentietypen die in " "gebruik zijn" -#: awx/api/serializers.py:1940 +#: awx/api/serializers.py:1961 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "Moet 'cloud' of 'net' zijn, niet %s" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1967 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "'ask_at_runtime' wordt niet ondersteund voor aangepaste referenties." -#: awx/api/serializers.py:2119 +#: awx/api/serializers.py:2140 #, python-format msgid "\"%s\" is not a valid choice" msgstr "\"%s\" is geen geldige keuze" -#: awx/api/serializers.py:2138 +#: awx/api/serializers.py:2159 #, python-format msgid "'%s' is not a valid field for %s" msgstr "'%s' is geen geldig veld voor %s" -#: awx/api/serializers.py:2150 +#: awx/api/serializers.py:2180 +msgid "" +"You cannot change the credential type of the credential, as it may break the" +" functionality of the resources using it." +msgstr "" +"U kunt het soort toegangsgegevens niet wijzigen, omdat dan de bronnen die " +"deze gebruiken niet langer werken." + +#: awx/api/serializers.py:2191 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." @@ -350,7 +367,7 @@ msgstr "" "de eigenaarrol. Indien verschaft, geef geen team of organisatie op. Alleen " "geldig voor maken." -#: awx/api/serializers.py:2155 +#: awx/api/serializers.py:2196 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." @@ -359,7 +376,7 @@ msgstr "" "te voegen. Indien verschaft, geef geen gebruiker of organisatie op. Alleen " "geldig voor maken." -#: awx/api/serializers.py:2160 +#: awx/api/serializers.py:2201 msgid "" "Inherit permissions from organization roles. If provided on creation, do not" " give either user or team." @@ -367,124 +384,128 @@ msgstr "" "Neem machtigingen over van organisatierollen. Indien verschaft bij maken, " "geef geen gebruiker of team op." -#: awx/api/serializers.py:2176 +#: awx/api/serializers.py:2217 msgid "Missing 'user', 'team', or 'organization'." msgstr "'gebruiker', 'team' of 'organisatie' ontbreekt." -#: awx/api/serializers.py:2216 +#: awx/api/serializers.py:2257 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "" "Referentieorganisatie moet worden ingesteld en moet overeenkomen vóór " "toewijzing aan een team" -#: awx/api/serializers.py:2378 +#: awx/api/serializers.py:2424 msgid "You must provide a cloud credential." msgstr "U moet een cloudreferentie opgeven." -#: awx/api/serializers.py:2379 +#: awx/api/serializers.py:2425 msgid "You must provide a network credential." msgstr "U moet een netwerkreferentie opgeven." -#: awx/api/serializers.py:2395 +#: awx/api/serializers.py:2441 msgid "This field is required." msgstr "Dit veld is vereist." -#: awx/api/serializers.py:2397 awx/api/serializers.py:2399 +#: awx/api/serializers.py:2443 awx/api/serializers.py:2445 msgid "Playbook not found for project." msgstr "Draaiboek is niet gevonden voor project." -#: awx/api/serializers.py:2401 +#: awx/api/serializers.py:2447 msgid "Must select playbook for project." msgstr "Moet een draaiboek selecteren voor het project." -#: awx/api/serializers.py:2476 +#: awx/api/serializers.py:2522 msgid "Must either set a default value or ask to prompt on launch." msgstr "" "Moet een standaardwaarde instellen of hierom laten vragen bij het opstarten." -#: awx/api/serializers.py:2478 awx/main/models/jobs.py:325 +#: awx/api/serializers.py:2524 awx/main/models/jobs.py:326 msgid "Job types 'run' and 'check' must have assigned a project." msgstr "" "Aan de taaktypen 'uitvoeren' en 'controleren' moet een project zijn " "toegewezen." -#: awx/api/serializers.py:2549 +#: awx/api/serializers.py:2611 msgid "Invalid job template." msgstr "Ongeldige taaksjabloon." -#: awx/api/serializers.py:2630 -msgid "Credential not found or deleted." -msgstr "Referentie niet gevonden of verwijderd." +#: awx/api/serializers.py:2708 +msgid "Neither credential nor vault credential provided." +msgstr "Geen toegangsgegevens of toegangsgegevens voor de kluis ingevoerd." -#: awx/api/serializers.py:2632 +#: awx/api/serializers.py:2711 msgid "Job Template Project is missing or undefined." msgstr "Het taaksjabloonproject ontbreekt of is niet gedefinieerd." -#: awx/api/serializers.py:2634 +#: awx/api/serializers.py:2713 msgid "Job Template Inventory is missing or undefined." msgstr "De taaksjablooninventaris ontbreekt of is niet gedefinieerd." -#: awx/api/serializers.py:2921 +#: awx/api/serializers.py:2782 awx/main/tasks.py:2186 +msgid "{} are prohibited from use in ad hoc commands." +msgstr "{} kunnen niet worden gebruikt in ad-hocopdrachten." + +#: awx/api/serializers.py:3008 #, python-format msgid "%(job_type)s is not a valid job type. The choices are %(choices)s." msgstr "%(job_type)s is geen geldig taaktype. De keuzes zijn %(choices)s." -#: awx/api/serializers.py:2926 +#: awx/api/serializers.py:3013 msgid "Workflow job template is missing during creation." msgstr "De taaksjabloon voor de workflow ontbreekt tijdens het maken." -#: awx/api/serializers.py:2931 +#: awx/api/serializers.py:3018 #, python-format msgid "Cannot nest a %s inside a WorkflowJobTemplate" msgstr "Kan geen a %s nesten in een WorkflowJobTemplate" -#: awx/api/serializers.py:3188 +#: awx/api/serializers.py:3291 #, python-format msgid "Job Template '%s' is missing or undefined." msgstr "Taaksjabloon '%s' ontbreekt of is niet gedefinieerd." -#: awx/api/serializers.py:3191 +#: awx/api/serializers.py:3294 msgid "The inventory associated with this Job Template is being deleted." msgstr "De aan deze taaksjabloon gekoppelde inventaris wordt verwijderd." -#: awx/api/serializers.py:3232 awx/api/views.py:2991 +#: awx/api/serializers.py:3335 awx/api/views.py:3023 #, python-format msgid "Cannot assign multiple %s credentials." msgstr "Kan niet meerdere referenties voor %s toewijzen." -#: awx/api/serializers.py:3234 awx/api/views.py:2994 +#: awx/api/serializers.py:3337 awx/api/views.py:3026 msgid "Extra credentials must be network or cloud." msgstr "Extra referenties moeten netwerk of cloud zijn." -#: awx/api/serializers.py:3371 +#: awx/api/serializers.py:3474 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" "Ontbrekende vereiste velden voor kennisgevingsconfiguratie: " "notification_type" -#: awx/api/serializers.py:3394 +#: awx/api/serializers.py:3497 msgid "No values specified for field '{}'" msgstr "Geen waarden opgegeven voor veld '{}'" -#: awx/api/serializers.py:3399 +#: awx/api/serializers.py:3502 msgid "Missing required fields for Notification Configuration: {}." msgstr "Ontbrekende vereiste velden voor kennisgevingsconfiguratie: {}." -#: awx/api/serializers.py:3402 +#: awx/api/serializers.py:3505 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "Configuratieveld '{}' onjuist type, {} verwacht." -#: awx/api/serializers.py:3455 +#: awx/api/serializers.py:3558 msgid "Inventory Source must be a cloud resource." msgstr "Inventarisbron moet een cloudresource zijn." -#: awx/api/serializers.py:3457 +#: awx/api/serializers.py:3560 msgid "Manual Project cannot have a schedule set." msgstr "Handmatig project kan geen ingesteld schema hebben." -#: awx/api/serializers.py:3460 +#: awx/api/serializers.py:3563 msgid "" "Inventory sources with `update_on_project_update` cannot be scheduled. " "Schedule its source project `{}` instead." @@ -492,71 +513,71 @@ msgstr "" "Inventarisbronnen met `update_on_project_update` kunnen niet worden gepland." " Plan in plaats daarvan het bijbehorende bronproject `{}`." -#: awx/api/serializers.py:3479 +#: awx/api/serializers.py:3582 msgid "Projects and inventory updates cannot accept extra variables." msgstr "" "Projecten en inventarisupdates kunnen geen extra variabelen accepteren." -#: awx/api/serializers.py:3501 +#: awx/api/serializers.py:3604 msgid "" "DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ" msgstr "" "DTSTART vereist in rrule. De waarde moet overeenkomen: " "DTSTART:YYYYMMDDTHHMMSSZ" -#: awx/api/serializers.py:3503 +#: awx/api/serializers.py:3606 msgid "Multiple DTSTART is not supported." msgstr "Meervoudige DTSTART wordt niet ondersteund." -#: awx/api/serializers.py:3505 +#: awx/api/serializers.py:3608 msgid "RRULE require in rrule." msgstr "RRULE vereist in rrule." -#: awx/api/serializers.py:3507 +#: awx/api/serializers.py:3610 msgid "Multiple RRULE is not supported." msgstr "Meervoudige RRULE wordt niet ondersteund." -#: awx/api/serializers.py:3509 +#: awx/api/serializers.py:3612 msgid "INTERVAL required in rrule." msgstr "INTERVAL is vereist in rrule." -#: awx/api/serializers.py:3511 +#: awx/api/serializers.py:3614 msgid "TZID is not supported." msgstr "TZID wordt niet ondersteund." -#: awx/api/serializers.py:3513 +#: awx/api/serializers.py:3616 msgid "SECONDLY is not supported." msgstr "SECONDLY wordt niet ondersteund." -#: awx/api/serializers.py:3515 +#: awx/api/serializers.py:3618 msgid "Multiple BYMONTHDAYs not supported." msgstr "Meerdere BYMONTHDAY's worden niet ondersteund." -#: awx/api/serializers.py:3517 +#: awx/api/serializers.py:3620 msgid "Multiple BYMONTHs not supported." msgstr "Meerdere BYMONTH's worden niet ondersteund." -#: awx/api/serializers.py:3519 +#: awx/api/serializers.py:3622 msgid "BYDAY with numeric prefix not supported." msgstr "BYDAY met numeriek voorvoegsel wordt niet ondersteund." -#: awx/api/serializers.py:3521 +#: awx/api/serializers.py:3624 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY wordt niet ondersteund." -#: awx/api/serializers.py:3523 +#: awx/api/serializers.py:3626 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO wordt niet ondersteund." -#: awx/api/serializers.py:3527 +#: awx/api/serializers.py:3630 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 wordt niet ondersteund." -#: awx/api/serializers.py:3531 +#: awx/api/serializers.py:3634 msgid "rrule parsing failed validation." msgstr "De validering van rrule-parsering is mislukt." -#: awx/api/serializers.py:3631 +#: awx/api/serializers.py:3760 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" @@ -564,7 +585,7 @@ msgstr "" "Een overzicht van de nieuwe en gewijzigde waarden wanneer een object wordt " "gemaakt, bijgewerkt of verwijderd" -#: awx/api/serializers.py:3633 +#: awx/api/serializers.py:3762 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " @@ -574,7 +595,7 @@ msgstr "" "objecttype. Voor koppel- en ontkoppel-gebeurtenissen is dit het objecttype " "dat wordt gekoppeld aan of ontkoppeld van object2." -#: awx/api/serializers.py:3636 +#: awx/api/serializers.py:3765 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated" @@ -584,165 +605,165 @@ msgstr "" "en ontkoppel-gebeurtenissen is dit het objecttype waaraan object1 wordt " "gekoppeld." -#: awx/api/serializers.py:3639 +#: awx/api/serializers.py:3768 msgid "The action taken with respect to the given object(s)." msgstr "De actie ondernomen met betrekking tot de gegeven objecten." -#: awx/api/serializers.py:3749 +#: awx/api/serializers.py:3885 msgid "Unable to login with provided credentials." msgstr "Kan niet aanmelden met de verschafte referenties." -#: awx/api/serializers.py:3751 +#: awx/api/serializers.py:3887 msgid "Must include \"username\" and \"password\"." msgstr "Moet \"gebruikersnaam\" en \"wachtwoord\" bevatten." -#: awx/api/views.py:106 +#: awx/api/views.py:108 msgid "Your license does not allow use of the activity stream." msgstr "Uw licentie staat het gebruik van de activiteitenstroom niet toe." -#: awx/api/views.py:116 +#: awx/api/views.py:118 msgid "Your license does not permit use of system tracking." msgstr "Uw licentie staat het gebruik van systeemtracking niet toe." -#: awx/api/views.py:126 +#: awx/api/views.py:128 msgid "Your license does not allow use of workflows." msgstr "Uw licentie staat het gebruik van workflows niet toe." -#: awx/api/views.py:140 +#: awx/api/views.py:142 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" "Kan taakresource niet verwijderen wanneer een gekoppelde workflowtaak wordt " "uitgevoerd." -#: awx/api/views.py:144 +#: awx/api/views.py:146 msgid "Cannot delete running job resource." msgstr "Kan geen taakbron in uitvoering verwijderen." -#: awx/api/views.py:153 awx/templates/rest_framework/api.html:28 +#: awx/api/views.py:155 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "REST API" -#: awx/api/views.py:162 awx/templates/rest_framework/api.html:4 +#: awx/api/views.py:164 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "AWX REST API" -#: awx/api/views.py:224 +#: awx/api/views.py:226 msgid "Version 1" msgstr "Versie 1" -#: awx/api/views.py:228 +#: awx/api/views.py:230 msgid "Version 2" msgstr "Versie 2" -#: awx/api/views.py:239 +#: awx/api/views.py:241 msgid "Ping" msgstr "Ping" -#: awx/api/views.py:270 awx/conf/apps.py:12 +#: awx/api/views.py:272 awx/conf/apps.py:12 msgid "Configuration" msgstr "Configuratie" -#: awx/api/views.py:323 +#: awx/api/views.py:325 msgid "Invalid license data" msgstr "Ongeldige licentiegegevens" -#: awx/api/views.py:325 +#: awx/api/views.py:327 msgid "Missing 'eula_accepted' property" msgstr "Ontbrekende eigenschap 'eula_accepted'" -#: awx/api/views.py:329 +#: awx/api/views.py:331 msgid "'eula_accepted' value is invalid" msgstr "Waarde 'eula_accepted' is ongeldig" -#: awx/api/views.py:332 +#: awx/api/views.py:334 msgid "'eula_accepted' must be True" msgstr "'eula_accepted' moet True zijn" -#: awx/api/views.py:339 +#: awx/api/views.py:341 msgid "Invalid JSON" msgstr "Ongeldig JSON" -#: awx/api/views.py:347 +#: awx/api/views.py:349 msgid "Invalid License" msgstr "Ongeldige licentie" -#: awx/api/views.py:357 +#: awx/api/views.py:359 msgid "Invalid license" msgstr "Ongeldige licentie" -#: awx/api/views.py:365 +#: awx/api/views.py:367 #, python-format msgid "Failed to remove license (%s)" msgstr "Kan licentie niet verwijderen (%s)" -#: awx/api/views.py:370 +#: awx/api/views.py:372 msgid "Dashboard" msgstr "Dashboard" -#: awx/api/views.py:469 +#: awx/api/views.py:471 msgid "Dashboard Jobs Graphs" msgstr "Dashboardtaakgrafieken" -#: awx/api/views.py:505 +#: awx/api/views.py:507 #, python-format msgid "Unknown period \"%s\"" msgstr "Onbekende periode \"%s\"" -#: awx/api/views.py:519 +#: awx/api/views.py:521 msgid "Instances" msgstr "Instanties" -#: awx/api/views.py:527 +#: awx/api/views.py:529 msgid "Instance Detail" msgstr "Instantiedetails" -#: awx/api/views.py:535 +#: awx/api/views.py:537 msgid "Instance Running Jobs" msgstr "Taken in uitvoering van instantie" -#: awx/api/views.py:550 +#: awx/api/views.py:552 msgid "Instance's Instance Groups" msgstr "Instantiegroepen van instantie" -#: awx/api/views.py:560 +#: awx/api/views.py:562 msgid "Instance Groups" msgstr "Instantiegroepen" -#: awx/api/views.py:568 +#: awx/api/views.py:570 msgid "Instance Group Detail" msgstr "Details van instantiegroep" -#: awx/api/views.py:576 +#: awx/api/views.py:578 msgid "Instance Group Running Jobs" msgstr "Taken in uitvoering van instantiegroep" -#: awx/api/views.py:586 +#: awx/api/views.py:588 msgid "Instance Group's Instances" msgstr "Instanties van instantiegroep" -#: awx/api/views.py:596 +#: awx/api/views.py:598 msgid "Schedules" msgstr "Schema's" -#: awx/api/views.py:615 +#: awx/api/views.py:617 msgid "Schedule Jobs List" msgstr "Schema takenlijst" -#: awx/api/views.py:841 +#: awx/api/views.py:843 msgid "Your license only permits a single organization to exist." msgstr "Uw licentie staat het gebruik van maar één organisatie toe." -#: awx/api/views.py:1077 awx/api/views.py:4615 +#: awx/api/views.py:1079 awx/api/views.py:4666 msgid "You cannot assign an Organization role as a child role for a Team." msgstr "" "U kunt een organisatierol niet toewijzen als een onderliggende rol voor een " "team." -#: awx/api/views.py:1081 awx/api/views.py:4629 +#: awx/api/views.py:1083 awx/api/views.py:4680 msgid "You cannot grant system-level permissions to a team." msgstr "U kunt een team geen rechten op systeemniveau verlenen." -#: awx/api/views.py:1088 awx/api/views.py:4621 +#: awx/api/views.py:1090 awx/api/views.py:4672 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" @@ -750,36 +771,36 @@ msgstr "" "U kunt een team geen referentietoegang verlenen wanneer het veld Organisatie" " niet is ingesteld of behoort tot een andere organisatie" -#: awx/api/views.py:1178 +#: awx/api/views.py:1180 msgid "Cannot delete project." msgstr "Kan project niet verwijderen." -#: awx/api/views.py:1213 +#: awx/api/views.py:1215 msgid "Project Schedules" msgstr "Projectschema's" -#: awx/api/views.py:1225 +#: awx/api/views.py:1227 msgid "Project SCM Inventory Sources" msgstr "SCM-inventarisbronnen van project" -#: awx/api/views.py:1352 +#: awx/api/views.py:1354 msgid "Project Update SCM Inventory Updates" msgstr "SCM-inventarisupdates van projectupdate" -#: awx/api/views.py:1407 +#: awx/api/views.py:1409 msgid "Me" msgstr "Mij" -#: awx/api/views.py:1451 awx/api/views.py:4572 +#: awx/api/views.py:1453 awx/api/views.py:4623 msgid "You may not perform any action with your own admin_role." msgstr "U kunt geen actie uitvoeren met uw eigen admin_role." -#: awx/api/views.py:1457 awx/api/views.py:4576 +#: awx/api/views.py:1459 awx/api/views.py:4627 msgid "You may not change the membership of a users admin_role" msgstr "" "U kunt het lidmaatschap van de admin_role van een gebruiker niet wijzigen" -#: awx/api/views.py:1462 awx/api/views.py:4581 +#: awx/api/views.py:1464 awx/api/views.py:4632 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" @@ -787,52 +808,53 @@ msgstr "" "U kunt geen referentietoegang verlenen aan een gebruiker die niet tot de " "organisatie van de referenties behoort" -#: awx/api/views.py:1466 awx/api/views.py:4585 +#: awx/api/views.py:1468 awx/api/views.py:4636 msgid "You cannot grant private credential access to another user" msgstr "U kunt geen privéreferentietoegang verlenen aan een andere gebruiker" -#: awx/api/views.py:1564 +#: awx/api/views.py:1566 #, python-format msgid "Cannot change %s." msgstr "Kan %s niet wijzigen." -#: awx/api/views.py:1570 +#: awx/api/views.py:1572 msgid "Cannot delete user." msgstr "Kan gebruiker niet verwijderen." -#: awx/api/views.py:1599 +#: awx/api/views.py:1601 msgid "Deletion not allowed for managed credential types" msgstr "Verwijdering is niet toegestaan voor beheerde referentietypen" -#: awx/api/views.py:1601 +#: awx/api/views.py:1603 msgid "Credential types that are in use cannot be deleted" msgstr "Referentietypen die in gebruik zijn, kunnen niet worden verwijderd" -#: awx/api/views.py:1779 +#: awx/api/views.py:1781 msgid "Cannot delete inventory script." msgstr "Kan inventarisscript niet verwijderen." -#: awx/api/views.py:1864 +#: awx/api/views.py:1866 +#, python-brace-format msgid "{0}" msgstr "{0}" -#: awx/api/views.py:2089 +#: awx/api/views.py:2101 msgid "Fact not found." msgstr "Feit niet gevonden." -#: awx/api/views.py:2113 +#: awx/api/views.py:2125 msgid "SSLError while trying to connect to {}" msgstr "SSLError tijdens poging om verbinding te maken met {}" -#: awx/api/views.py:2115 +#: awx/api/views.py:2127 msgid "Request to {} timed out." msgstr "Er is een time-out opgetreden voor de aanvraag naar {}" -#: awx/api/views.py:2117 +#: awx/api/views.py:2129 msgid "Unkown exception {} while trying to GET {}" msgstr "Onbekende uitzondering {} tijdens poging tot OPHALEN {}" -#: awx/api/views.py:2120 +#: awx/api/views.py:2132 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." @@ -840,7 +862,7 @@ msgstr "" "Geen toegang. Controleer uw Insights Credential gebruikersnaam en " "wachtwoord." -#: awx/api/views.py:2122 +#: awx/api/views.py:2134 msgid "" "Failed to gather reports and maintenance plans from Insights API at URL {}. " "Server responded with {} status code and message {}" @@ -848,201 +870,216 @@ msgstr "" "Kan rapporten en onderhoudsplannen niet verzamelen uit Insights API op URL " "{}. Server heeft gereageerd met statuscode {} en bericht {}" -#: awx/api/views.py:2128 +#: awx/api/views.py:2140 msgid "Expected JSON response from Insights but instead got {}" msgstr "Verwachtte JSON-reactie van Insights, maar kreeg in plaats daarvan {}" -#: awx/api/views.py:2135 +#: awx/api/views.py:2147 msgid "This host is not recognized as an Insights host." msgstr "Deze host wordt niet herkend als een Insights-host." -#: awx/api/views.py:2140 +#: awx/api/views.py:2152 msgid "The Insights Credential for \"{}\" was not found." msgstr "De Insights-referentie voor \"{}\" is niet gevonden." -#: awx/api/views.py:2209 +#: awx/api/views.py:2221 msgid "Cyclical Group association." msgstr "Cyclische groepskoppeling." -#: awx/api/views.py:2482 +#: awx/api/views.py:2499 msgid "Inventory Source List" msgstr "Lijst met inventarisbronnen" -#: awx/api/views.py:2495 +#: awx/api/views.py:2512 msgid "Inventory Sources Update" msgstr "Update van inventarisbronnen" -#: awx/api/views.py:2525 +#: awx/api/views.py:2542 msgid "Could not start because `can_update` returned False" msgstr "Kan niet starten omdat 'can_update' False heeft geretourneerd" -#: awx/api/views.py:2533 +#: awx/api/views.py:2550 msgid "No inventory sources to update." msgstr "Er zijn geen inventarisbronnen om bij te werken." -#: awx/api/views.py:2565 +#: awx/api/views.py:2582 msgid "Cannot delete inventory source." msgstr "Kan inventarisbron niet verwijderen." -#: awx/api/views.py:2573 +#: awx/api/views.py:2590 msgid "Inventory Source Schedules" msgstr "Inventarisbronschema's" -#: awx/api/views.py:2603 +#: awx/api/views.py:2620 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" "Berichtsjablonen kunnen alleen worden toegewezen wanneer de bron een van {} " "is." -#: awx/api/views.py:2826 +#: awx/api/views.py:2851 msgid "Job Template Schedules" msgstr "Taaksjabloonschema's" -#: awx/api/views.py:2846 awx/api/views.py:2862 +#: awx/api/views.py:2871 awx/api/views.py:2882 msgid "Your license does not allow adding surveys." msgstr "Uw licentie staat toevoeging van enquêtes niet toe." -#: awx/api/views.py:2869 +#: awx/api/views.py:2889 msgid "'name' missing from survey spec." msgstr "'name' ontbreekt in de enquêtespecificaties." -#: awx/api/views.py:2871 +#: awx/api/views.py:2891 msgid "'description' missing from survey spec." msgstr "'description' ontbreekt in de enquêtespecificaties." -#: awx/api/views.py:2873 +#: awx/api/views.py:2893 msgid "'spec' missing from survey spec." msgstr "'spec' ontbreekt in de enquêtespecificaties." -#: awx/api/views.py:2875 +#: awx/api/views.py:2895 msgid "'spec' must be a list of items." msgstr "'spec' moet een lijst met items zijn." -#: awx/api/views.py:2877 +#: awx/api/views.py:2897 msgid "'spec' doesn't contain any items." msgstr "'spec' bevat geen items." -#: awx/api/views.py:2883 +#: awx/api/views.py:2903 #, python-format msgid "Survey question %s is not a json object." msgstr "Enquêtevraag %s is geen json-object." -#: awx/api/views.py:2885 +#: awx/api/views.py:2905 #, python-format msgid "'type' missing from survey question %s." msgstr "'type' ontbreekt in enquêtevraag %s." -#: awx/api/views.py:2887 +#: awx/api/views.py:2907 #, python-format msgid "'question_name' missing from survey question %s." msgstr "'question_name' ontbreekt in enquêtevraag %s." -#: awx/api/views.py:2889 +#: awx/api/views.py:2909 #, python-format msgid "'variable' missing from survey question %s." msgstr "'variable' ontbreekt in enquêtevraag %s." -#: awx/api/views.py:2891 +#: awx/api/views.py:2911 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "'variable' '%(item)s' gedupliceerd in enquêtevraag %(survey)s." -#: awx/api/views.py:2896 +#: awx/api/views.py:2916 #, python-format msgid "'required' missing from survey question %s." msgstr "'required' ontbreekt in enquêtevraag %s." -#: awx/api/views.py:2901 +#: awx/api/views.py:2921 +#, python-brace-format msgid "" -"$encrypted$ is reserved keyword and may not be used as a default for " -"password {}." +"Value {question_default} for '{variable_name}' expected to be a string." msgstr "" -"$encrypted$ is een gereserveerd sleutelwoord en mag niet als standaardwaarde" -" worden gebruikt voor wachtwoord {}." +"Waarde {question_default} voor '{variable_name}' zou een string moeten zijn." -#: awx/api/views.py:3017 +#: awx/api/views.py:2928 +#, python-brace-format +msgid "" +"$encrypted$ is reserved keyword for password questions and may not be used " +"as a default for '{variable_name}' in survey question {question_position}." +msgstr "" +"$encrypted$ is een gereserveerd sleutelwoord voor het wachtwoord en mag niet" +" als standaardwaarde worden gebruikt voor '{variable_name}' in enquêtevraag " +"{question_position}." + +#: awx/api/views.py:3049 msgid "Maximum number of labels for {} reached." msgstr "Het maximumaantal labels voor {} is bereikt." -#: awx/api/views.py:3136 +#: awx/api/views.py:3170 msgid "No matching host could be found!" msgstr "Er is geen overeenkomende host gevonden." -#: awx/api/views.py:3139 +#: awx/api/views.py:3173 msgid "Multiple hosts matched the request!" msgstr "Meerdere hosts kwamen overeen met de aanvraag." -#: awx/api/views.py:3144 +#: awx/api/views.py:3178 msgid "Cannot start automatically, user input required!" msgstr "Kan niet automatisch starten. Gebruikersinput is vereist." -#: awx/api/views.py:3151 +#: awx/api/views.py:3185 msgid "Host callback job already pending." msgstr "Er is al een hostterugkoppelingstaak in afwachting." -#: awx/api/views.py:3164 +#: awx/api/views.py:3199 msgid "Error starting job!" msgstr "Fout bij starten taak." -#: awx/api/views.py:3271 +#: awx/api/views.py:3306 +#, python-brace-format msgid "Cannot associate {0} when {1} have been associated." msgstr "Kan {0} niet koppelen wanneer {1} zijn gekoppeld." -#: awx/api/views.py:3296 +#: awx/api/views.py:3331 msgid "Multiple parent relationship not allowed." msgstr "Relatie met meerdere bovenliggende elementen is niet toegestaan." -#: awx/api/views.py:3301 +#: awx/api/views.py:3336 msgid "Cycle detected." msgstr "Cyclus gedetecteerd." -#: awx/api/views.py:3505 +#: awx/api/views.py:3540 msgid "Workflow Job Template Schedules" msgstr "Taaksjabloonschema's voor workflows" -#: awx/api/views.py:3650 awx/api/views.py:4217 +#: awx/api/views.py:3685 awx/api/views.py:4268 msgid "Superuser privileges needed." msgstr "Supergebruikersbevoegdheden vereist." -#: awx/api/views.py:3682 +#: awx/api/views.py:3717 msgid "System Job Template Schedules" msgstr "Taaksjabloonschema's voor systeem" -#: awx/api/views.py:3891 +#: awx/api/views.py:3780 +msgid "POST not allowed for Job launching in version 2 of the api" +msgstr "" +"POST is niet toegestaan voor het openen van taken in versie 2 van de API" + +#: awx/api/views.py:3942 msgid "Job Host Summaries List" msgstr "Lijst met taakhostoverzichten" -#: awx/api/views.py:3938 +#: awx/api/views.py:3989 msgid "Job Event Children List" msgstr "Lijst met onderliggende taakgebeurteniselementen" -#: awx/api/views.py:3947 +#: awx/api/views.py:3998 msgid "Job Event Hosts List" msgstr "Lijst met taakgebeurtenishosts" -#: awx/api/views.py:3957 +#: awx/api/views.py:4008 msgid "Job Events List" msgstr "Lijst met taakgebeurtenissen" -#: awx/api/views.py:4171 +#: awx/api/views.py:4222 msgid "Ad Hoc Command Events List" msgstr "Lijst met ad-hoc-opdrachtgebeurtenissen" -#: awx/api/views.py:4386 +#: awx/api/views.py:4437 msgid "Error generating stdout download file: {}" msgstr "Fout bij genereren stdout-downloadbestand: {}" -#: awx/api/views.py:4399 +#: awx/api/views.py:4450 #, python-format msgid "Error generating stdout download file: %s" msgstr "Fout bij genereren stdout-downloadbestand: %s" -#: awx/api/views.py:4444 +#: awx/api/views.py:4495 msgid "Delete not allowed while there are pending notifications" msgstr "" "Verwijderen is niet toegestaan wanneer er berichten in afwachting zijn" -#: awx/api/views.py:4451 +#: awx/api/views.py:4502 msgid "Notification Template Test" msgstr "Berichtsjabloon" @@ -1199,15 +1236,16 @@ msgstr "Voorbeeld van instelling die anders kan zijn voor elke gebruiker." msgid "User" msgstr "Gebruiker" -#: awx/conf/fields.py:62 +#: awx/conf/fields.py:63 msgid "Enter a valid URL" msgstr "Geef een geldige URL op" -#: awx/conf/fields.py:94 +#: awx/conf/fields.py:95 +#, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input} is geen geldige tekenreeks." -#: awx/conf/license.py:19 +#: awx/conf/license.py:22 msgid "Your Tower license does not allow that." msgstr "Uw Tower-licentie staat dat niet toe." @@ -1227,7 +1265,13 @@ msgstr "" msgid "Skip commenting out settings in files." msgstr "Sla het becommentariëren van instellingen in bestanden over." -#: awx/conf/management/commands/migrate_to_database_settings.py:61 +#: awx/conf/management/commands/migrate_to_database_settings.py:62 +msgid "Skip migrating and only comment out settings in files." +msgstr "" +"Sla migreren over en maak alleen opmerkingen over de instellingen in " +"bestanden." + +#: awx/conf/management/commands/migrate_to_database_settings.py:68 msgid "Backup existing settings files with this suffix." msgstr "" "Maak een back-up van bestaande instellingenbestanden met dit achtervoegsel." @@ -1252,7 +1296,7 @@ msgstr "Gewijzigd" msgid "User-Defaults" msgstr "Standaardinstellingen voor gebruiker" -#: awx/conf/registry.py:151 +#: awx/conf/registry.py:154 msgid "This value has been set manually in a settings file." msgstr "Deze waarde is handmatig ingesteld in een instellingenbestand." @@ -1324,94 +1368,92 @@ msgstr "Instellingsdetail" msgid "Logging Connectivity Test" msgstr "Connectiviteitstest logboekregistratie" -#: awx/main/access.py:224 +#: awx/main/access.py:44 +msgid "Resource is being used by running jobs." +msgstr "Bron wordt gebruikt om taken uit te voeren." + +#: awx/main/access.py:237 #, python-format msgid "Bad data found in related field %s." msgstr "Ongeldige gegevens gevonden in gerelateerd veld %s." -#: awx/main/access.py:268 +#: awx/main/access.py:281 msgid "License is missing." msgstr "Licentie ontbreekt." -#: awx/main/access.py:270 +#: awx/main/access.py:283 msgid "License has expired." msgstr "Licentie is verlopen." -#: awx/main/access.py:278 +#: awx/main/access.py:291 #, python-format msgid "License count of %s instances has been reached." msgstr "Het aantal licenties van %s instanties is bereikt." -#: awx/main/access.py:280 +#: awx/main/access.py:293 #, python-format msgid "License count of %s instances has been exceeded." msgstr "Het aantal licenties van %s instanties is overschreden." -#: awx/main/access.py:282 +#: awx/main/access.py:295 msgid "Host count exceeds available instances." msgstr "Het aantal hosts is groter dan het aantal instanties." -#: awx/main/access.py:286 +#: awx/main/access.py:299 #, python-format msgid "Feature %s is not enabled in the active license." msgstr "De functie %s is niet ingeschakeld in de actieve licentie." -#: awx/main/access.py:288 +#: awx/main/access.py:301 msgid "Features not found in active license." msgstr "Functies niet gevonden in actieve licentie." -#: awx/main/access.py:534 awx/main/access.py:619 awx/main/access.py:748 -#: awx/main/access.py:801 awx/main/access.py:1063 awx/main/access.py:1263 -#: awx/main/access.py:1725 -msgid "Resource is being used by running jobs" -msgstr "Resource wordt gebruikt door taken uit te voeren" - -#: awx/main/access.py:675 +#: awx/main/access.py:707 msgid "Unable to change inventory on a host." msgstr "Kan inventaris op een host niet wijzigen." -#: awx/main/access.py:692 awx/main/access.py:737 +#: awx/main/access.py:724 awx/main/access.py:769 msgid "Cannot associate two items from different inventories." msgstr "Kan twee items uit verschillende inventarissen niet koppelen." -#: awx/main/access.py:725 +#: awx/main/access.py:757 msgid "Unable to change inventory on a group." msgstr "Kan inventaris van een groep niet wijzigen." -#: awx/main/access.py:983 +#: awx/main/access.py:1017 msgid "Unable to change organization on a team." msgstr "Kan organisatie van een team niet wijzigen." -#: awx/main/access.py:996 +#: awx/main/access.py:1030 msgid "The {} role cannot be assigned to a team" msgstr "De rol {} kan niet worden toegewezen aan een team" -#: awx/main/access.py:998 +#: awx/main/access.py:1032 msgid "The admin_role for a User cannot be assigned to a team" msgstr "" "De admin_role voor een gebruiker kan niet worden toegewezen aan een team" -#: awx/main/access.py:1443 +#: awx/main/access.py:1479 msgid "Job has been orphaned from its job template." msgstr "De taak is verwijderd uit zijn taaksjabloon." -#: awx/main/access.py:1445 +#: awx/main/access.py:1481 msgid "You do not have execute permission to related job template." msgstr "U hebt geen uitvoermachtiging voor de gerelateerde taaksjabloon." -#: awx/main/access.py:1448 +#: awx/main/access.py:1484 msgid "Job was launched with prompted fields." msgstr "De taak is gestart met invoervelden." -#: awx/main/access.py:1450 +#: awx/main/access.py:1486 msgid " Organization level permissions required." msgstr "Organisatieniveaumachtigingen zijn vereist." -#: awx/main/access.py:1452 +#: awx/main/access.py:1488 msgid " You do not have permission to related resources." msgstr "U hebt geen machtiging voor gerelateerde resources." -#: awx/main/access.py:1798 +#: awx/main/access.py:1833 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." @@ -1945,86 +1987,86 @@ msgstr "" msgid "\n" msgstr "\n" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Sudo" msgstr "Sudo" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Su" msgstr "Su" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pbrun" msgstr "Pbrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pfexec" msgstr "Pfexec" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "DZDO" msgstr "DZDO" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Pmrun" msgstr "Pmrun" -#: awx/main/constants.py:8 +#: awx/main/constants.py:10 msgid "Runas" msgstr "Runas" -#: awx/main/fields.py:56 +#: awx/main/fields.py:57 #, python-format msgid "'%s' is not one of ['%s']" msgstr "'%s' is niet een van ['%s']" -#: awx/main/fields.py:531 +#: awx/main/fields.py:533 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "kan niet ingesteld worden, tenzij '%s' ingesteld is" -#: awx/main/fields.py:547 +#: awx/main/fields.py:549 #, python-format msgid "required for %s" msgstr "vereist voor %s" -#: awx/main/fields.py:571 +#: awx/main/fields.py:573 msgid "must be set when SSH key is encrypted." msgstr "moet worden ingesteld wanneer SSH-sleutel wordt versleuteld." -#: awx/main/fields.py:577 +#: awx/main/fields.py:579 msgid "should not be set when SSH key is not encrypted." msgstr "mag niet worden ingesteld wanneer SSH-sleutel niet is gecodeerd." -#: awx/main/fields.py:635 +#: awx/main/fields.py:637 msgid "'dependencies' is not supported for custom credentials." msgstr "" "'afhankelijkheden' is niet ondersteund voor aangepaste toegangsgegevens." -#: awx/main/fields.py:649 +#: awx/main/fields.py:651 msgid "\"tower\" is a reserved field name" msgstr "\"tower\" is een gereserveerde veldnaam" -#: awx/main/fields.py:656 +#: awx/main/fields.py:658 #, python-format msgid "field IDs must be unique (%s)" msgstr "veld-id's moeten uniek zijn (%s)" -#: awx/main/fields.py:669 +#: awx/main/fields.py:671 #, python-format msgid "%s not allowed for %s type (%s)" msgstr "%s niet toegestaan voor type %s (%s)" -#: awx/main/fields.py:753 +#: awx/main/fields.py:755 #, python-format msgid "%s uses an undefined field (%s)" msgstr "%s gebruikt een niet-gedefinieerd veld (%s)" -#: awx/main/middleware.py:121 +#: awx/main/middleware.py:157 msgid "Formats of all available named urls" msgstr "Indelingen van alle beschikbare, genoemde url's" -#: awx/main/middleware.py:122 +#: awx/main/middleware.py:158 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." @@ -2032,15 +2074,15 @@ msgstr "" "Alleen-lezen-lijst met sleutelwaardeparen die de standaardindeling van alle " "beschikbare, genoemde URL's toont." -#: awx/main/middleware.py:124 awx/main/middleware.py:134 +#: awx/main/middleware.py:160 awx/main/middleware.py:170 msgid "Named URL" msgstr "Genoemde URL" -#: awx/main/middleware.py:131 +#: awx/main/middleware.py:167 msgid "List of all named url graph nodes." msgstr "Lijst met alle grafische knooppunten van genoemde URL's." -#: awx/main/middleware.py:132 +#: awx/main/middleware.py:168 msgid "" "Read-only list of key-value pairs that exposes named URL graph topology. Use" " this list to programmatically generate named URLs for resources" @@ -2049,51 +2091,51 @@ msgstr "" "genoemde URL's duidelijk maakt. Gebruik deze lijst om programmatische " "genoemde URL's voor resources te genereren." -#: awx/main/migrations/_reencrypt.py:23 awx/main/models/notifications.py:33 +#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:33 msgid "Email" msgstr "E-mail" -#: awx/main/migrations/_reencrypt.py:24 awx/main/models/notifications.py:34 +#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:34 msgid "Slack" msgstr "Slack" -#: awx/main/migrations/_reencrypt.py:25 awx/main/models/notifications.py:35 +#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:35 msgid "Twilio" msgstr "Twilio" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:36 +#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:36 msgid "Pagerduty" msgstr "Pagerduty" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:37 +#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:37 msgid "HipChat" msgstr "HipChat" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:38 +#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:38 msgid "Webhook" msgstr "Webhook" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:39 +#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:39 msgid "IRC" msgstr "IRC" -#: awx/main/models/activity_stream.py:24 +#: awx/main/models/activity_stream.py:25 msgid "Entity Created" msgstr "Entiteit gemaakt" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:26 msgid "Entity Updated" msgstr "Entiteit bijgewerkt" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:27 msgid "Entity Deleted" msgstr "Entiteit verwijderd" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:28 msgid "Entity Associated with another Entity" msgstr "Entiteit gekoppeld aan een andere entiteit" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:29 msgid "Entity was Disassociated with another Entity" msgstr "Entiteit is losgekoppeld van een andere entiteit" @@ -2119,43 +2161,43 @@ msgstr "Niet-ondersteunde module voor ad-hocopdrachten." msgid "No argument passed to %s module." msgstr "Geen argument doorgegeven aan module %s." -#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:904 +#: awx/main/models/ad_hoc_commands.py:245 awx/main/models/jobs.py:911 msgid "Host Failed" msgstr "Host is mislukt" -#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:905 +#: awx/main/models/ad_hoc_commands.py:246 awx/main/models/jobs.py:912 msgid "Host OK" msgstr "Host OK" -#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:908 +#: awx/main/models/ad_hoc_commands.py:247 awx/main/models/jobs.py:915 msgid "Host Unreachable" msgstr "Host onbereikbaar" -#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:907 +#: awx/main/models/ad_hoc_commands.py:252 awx/main/models/jobs.py:914 msgid "Host Skipped" msgstr "Host overgeslagen" -#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:935 +#: awx/main/models/ad_hoc_commands.py:262 awx/main/models/jobs.py:942 msgid "Debug" msgstr "Foutopsporing" -#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:936 +#: awx/main/models/ad_hoc_commands.py:263 awx/main/models/jobs.py:943 msgid "Verbose" msgstr "Uitgebreid" -#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:937 +#: awx/main/models/ad_hoc_commands.py:264 awx/main/models/jobs.py:944 msgid "Deprecated" msgstr "Afgeschaft" -#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:938 +#: awx/main/models/ad_hoc_commands.py:265 awx/main/models/jobs.py:945 msgid "Warning" msgstr "Waarschuwing" -#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:939 +#: awx/main/models/ad_hoc_commands.py:266 awx/main/models/jobs.py:946 msgid "System Warning" msgstr "Systeemwaarschuwing" -#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:940 +#: awx/main/models/ad_hoc_commands.py:267 awx/main/models/jobs.py:947 #: awx/main/models/unified_jobs.py:64 msgid "Error" msgstr "Fout" @@ -2174,27 +2216,27 @@ msgstr "Controleren" msgid "Scan" msgstr "Scannen" -#: awx/main/models/credential.py:82 +#: awx/main/models/credential.py:86 msgid "Host" msgstr "Host" -#: awx/main/models/credential.py:83 +#: awx/main/models/credential.py:87 msgid "The hostname or IP address to use." msgstr "Te gebruiken hostnaam of IP-adres" -#: awx/main/models/credential.py:89 +#: awx/main/models/credential.py:93 msgid "Username" msgstr "Gebruikersnaam" -#: awx/main/models/credential.py:90 +#: awx/main/models/credential.py:94 msgid "Username for this credential." msgstr "Gebruikersnaam voor deze referentie." -#: awx/main/models/credential.py:96 +#: awx/main/models/credential.py:100 msgid "Password" msgstr "Wachtwoord" -#: awx/main/models/credential.py:97 +#: awx/main/models/credential.py:101 msgid "" "Password for this credential (or \"ASK\" to prompt the user for machine " "credentials)." @@ -2202,43 +2244,43 @@ msgstr "" "Wachtwoord voor deze referentie (of \"ASK\" om de gebruiker om de " "machinereferenties te vragen)." -#: awx/main/models/credential.py:104 +#: awx/main/models/credential.py:108 msgid "Security Token" msgstr "Beveiligingstoken" -#: awx/main/models/credential.py:105 +#: awx/main/models/credential.py:109 msgid "Security Token for this credential" msgstr "Beveiligingstoken voor deze referentie" -#: awx/main/models/credential.py:111 +#: awx/main/models/credential.py:115 msgid "Project" msgstr "Project" -#: awx/main/models/credential.py:112 +#: awx/main/models/credential.py:116 msgid "The identifier for the project." msgstr "De id voor het project." -#: awx/main/models/credential.py:118 +#: awx/main/models/credential.py:122 msgid "Domain" msgstr "Domein" -#: awx/main/models/credential.py:119 +#: awx/main/models/credential.py:123 msgid "The identifier for the domain." msgstr "De id voor het domein." -#: awx/main/models/credential.py:124 +#: awx/main/models/credential.py:128 msgid "SSH private key" msgstr "SSH-privésleutel" -#: awx/main/models/credential.py:125 +#: awx/main/models/credential.py:129 msgid "RSA or DSA private key to be used instead of password." msgstr "RSA- of DSA-privésleutel te gebruiken in plaats van wachtwoord." -#: awx/main/models/credential.py:131 +#: awx/main/models/credential.py:135 msgid "SSH key unlock" msgstr "SSH-sleutelontgrendeling" -#: awx/main/models/credential.py:132 +#: awx/main/models/credential.py:136 msgid "" "Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " "user for machine credentials)." @@ -2246,51 +2288,51 @@ msgstr "" "Wachtwoordzin om SSH-privésleutel te ontgrendelen indien versleuteld (of " "\"ASK\" om de gebruiker om de machinereferenties te vragen)." -#: awx/main/models/credential.py:139 +#: awx/main/models/credential.py:143 msgid "None" msgstr "Geen" -#: awx/main/models/credential.py:140 +#: awx/main/models/credential.py:144 msgid "Privilege escalation method." msgstr "Methode voor verhoging van rechten." -#: awx/main/models/credential.py:146 +#: awx/main/models/credential.py:150 msgid "Privilege escalation username." msgstr "Gebruikersnaam voor verhoging van rechten." -#: awx/main/models/credential.py:152 +#: awx/main/models/credential.py:156 msgid "Password for privilege escalation method." msgstr "Wachtwoord voor methode voor verhoging van rechten." -#: awx/main/models/credential.py:158 +#: awx/main/models/credential.py:162 msgid "Vault password (or \"ASK\" to prompt the user)." msgstr "Wachtwoord voor kluis (of \"ASK\" om de gebruiker te vragen)." -#: awx/main/models/credential.py:162 +#: awx/main/models/credential.py:166 msgid "Whether to use the authorize mechanism." msgstr "Of het autorisatiemechanisme mag worden gebruikt." -#: awx/main/models/credential.py:168 +#: awx/main/models/credential.py:172 msgid "Password used by the authorize mechanism." msgstr "Wachtwoord gebruikt door het autorisatiemechanisme." -#: awx/main/models/credential.py:174 +#: awx/main/models/credential.py:178 msgid "Client Id or Application Id for the credential" msgstr "Klant-id voor toepassings-id voor de referentie" -#: awx/main/models/credential.py:180 +#: awx/main/models/credential.py:184 msgid "Secret Token for this credential" msgstr "Geheim token voor deze referentie" -#: awx/main/models/credential.py:186 +#: awx/main/models/credential.py:190 msgid "Subscription identifier for this credential" msgstr "Abonnements-id voor deze referentie" -#: awx/main/models/credential.py:192 +#: awx/main/models/credential.py:196 msgid "Tenant identifier for this credential" msgstr "Huurder-id voor deze referentie" -#: awx/main/models/credential.py:216 +#: awx/main/models/credential.py:220 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." @@ -2298,7 +2340,7 @@ msgstr "" "Geef het type referentie op dat u wilt maken. Raadpleeg de documentatie voor" " Ansible Tower voor details over elk type." -#: awx/main/models/credential.py:230 awx/main/models/credential.py:416 +#: awx/main/models/credential.py:234 awx/main/models/credential.py:420 msgid "" "Enter inputs using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2308,31 +2350,31 @@ msgstr "" "wisselen tussen de twee. Raadpleeg de documentatie voor Ansible Tower voor " "voorbeeldsyntaxis." -#: awx/main/models/credential.py:397 +#: awx/main/models/credential.py:401 msgid "Machine" msgstr "Machine" -#: awx/main/models/credential.py:398 +#: awx/main/models/credential.py:402 msgid "Vault" msgstr "Kluis" -#: awx/main/models/credential.py:399 +#: awx/main/models/credential.py:403 msgid "Network" msgstr "Netwerk" -#: awx/main/models/credential.py:400 +#: awx/main/models/credential.py:404 msgid "Source Control" msgstr "Broncontrole" -#: awx/main/models/credential.py:401 +#: awx/main/models/credential.py:405 msgid "Cloud" msgstr "Cloud" -#: awx/main/models/credential.py:402 +#: awx/main/models/credential.py:406 msgid "Insights" msgstr "Inzichten" -#: awx/main/models/credential.py:423 +#: awx/main/models/credential.py:427 msgid "" "Enter injectors using either JSON or YAML syntax. Use the radio button to " "toggle between the two. Refer to the Ansible Tower documentation for example" @@ -2342,6 +2384,11 @@ msgstr "" " wisselen tussen de twee. Raadpleeg de documentatie voor Ansible Tower voor " "voorbeeldsyntaxis." +#: awx/main/models/credential.py:478 +#, python-format +msgid "adding %s credential type" +msgstr "%s soort toegangsgegevens toevoegen" + #: awx/main/models/fact.py:25 msgid "Host for the facts that the fact scan captured." msgstr "Host voor de feiten die de feitenscan heeft vastgelegd." @@ -2358,11 +2405,11 @@ msgstr "" "Willekeurige JSON-structuur van modulefeiten vastgelegd bij de tijdstempel " "voor één host." -#: awx/main/models/ha.py:76 +#: awx/main/models/ha.py:78 msgid "Instances that are members of this InstanceGroup" msgstr "Instanties die lid zijn van deze InstanceGroup" -#: awx/main/models/ha.py:81 +#: awx/main/models/ha.py:83 msgid "Instance Group to remotely control this group." msgstr "Instantiegroep om deze groep extern te regelen." @@ -2556,38 +2603,42 @@ msgid "Google Compute Engine" msgstr "Google Compute Engine" #: awx/main/models/inventory.py:870 -msgid "Microsoft Azure Classic (deprecated)" -msgstr "Microsoft Azure Classic (afgeschaft)" - -#: awx/main/models/inventory.py:871 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/inventory.py:872 +#: awx/main/models/inventory.py:871 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/inventory.py:873 +#: awx/main/models/inventory.py:872 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/inventory.py:874 +#: awx/main/models/inventory.py:873 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/inventory.py:875 +#: awx/main/models/inventory.py:874 msgid "OpenStack" msgstr "OpenStack" +#: awx/main/models/inventory.py:875 +msgid "oVirt4" +msgstr "oVirt4" + #: awx/main/models/inventory.py:876 +msgid "Ansible Tower" +msgstr "Ansible Tower" + +#: awx/main/models/inventory.py:877 msgid "Custom Script" msgstr "Aangepast script" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:994 msgid "Inventory source variables in YAML or JSON format." msgstr "Bronvariabelen inventaris in YAML- of JSON-indeling." -#: awx/main/models/inventory.py:1012 +#: awx/main/models/inventory.py:1013 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." @@ -2595,77 +2646,77 @@ msgstr "" "Door komma's gescheiden lijst met filterexpressies (alleen EC2). Hosts " "worden geïmporteerd wanneer WILLEKEURIG WELK van de filters overeenkomt." -#: awx/main/models/inventory.py:1018 +#: awx/main/models/inventory.py:1019 msgid "Limit groups automatically created from inventory source (EC2 only)." msgstr "" "Overschrijf groepen die automatisch gemaakt worden op grond van " "inventarisbron (alleen EC2)." -#: awx/main/models/inventory.py:1022 +#: awx/main/models/inventory.py:1023 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" "Overschrijf lokale groepen en hosts op grond van externe inventarisbron." -#: awx/main/models/inventory.py:1026 +#: awx/main/models/inventory.py:1027 msgid "Overwrite local variables from remote inventory source." msgstr "Overschrijf lokale variabelen op grond van externe inventarisbron." -#: awx/main/models/inventory.py:1031 awx/main/models/jobs.py:159 +#: awx/main/models/inventory.py:1032 awx/main/models/jobs.py:160 #: awx/main/models/projects.py:117 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "" "De hoeveelheid tijd (in seconden) voor uitvoering voordat de taak wordt " "geannuleerd." -#: awx/main/models/inventory.py:1064 +#: awx/main/models/inventory.py:1065 msgid "Image ID" msgstr "Image-id" -#: awx/main/models/inventory.py:1065 +#: awx/main/models/inventory.py:1066 msgid "Availability Zone" msgstr "Beschikbaarheidszone" -#: awx/main/models/inventory.py:1066 +#: awx/main/models/inventory.py:1067 msgid "Account" msgstr "Account" -#: awx/main/models/inventory.py:1067 +#: awx/main/models/inventory.py:1068 msgid "Instance ID" msgstr "Instantie-id" -#: awx/main/models/inventory.py:1068 +#: awx/main/models/inventory.py:1069 msgid "Instance State" msgstr "Instantiestaat" -#: awx/main/models/inventory.py:1069 +#: awx/main/models/inventory.py:1070 msgid "Instance Type" msgstr "Instantietype" -#: awx/main/models/inventory.py:1070 +#: awx/main/models/inventory.py:1071 msgid "Key Name" msgstr "Sleutelnaam" -#: awx/main/models/inventory.py:1071 +#: awx/main/models/inventory.py:1072 msgid "Region" msgstr "Regio" -#: awx/main/models/inventory.py:1072 +#: awx/main/models/inventory.py:1073 msgid "Security Group" msgstr "Beveiligingsgroep" -#: awx/main/models/inventory.py:1073 +#: awx/main/models/inventory.py:1074 msgid "Tags" msgstr "Tags" -#: awx/main/models/inventory.py:1074 +#: awx/main/models/inventory.py:1075 msgid "Tag None" msgstr "Tag geen" -#: awx/main/models/inventory.py:1075 +#: awx/main/models/inventory.py:1076 msgid "VPC ID" msgstr "VPC ID" -#: awx/main/models/inventory.py:1138 +#: awx/main/models/inventory.py:1145 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " @@ -2674,30 +2725,38 @@ msgstr "" "Cloudgebaseerde inventarisbronnen (zoals %s) vereisen referenties voor de " "overeenkomende cloudservice." -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:1152 msgid "Credential is required for a cloud source." msgstr "Referentie is vereist voor een cloudbron." -#: awx/main/models/inventory.py:1167 +#: awx/main/models/inventory.py:1155 +msgid "" +"Credentials of type machine, source control, insights and vault are " +"disallowed for custom inventory sources." +msgstr "" +"Toegangsgegevens van soort machine, bronbeheer, inzichten en kluis zijn niet" +" toegestaan voor aangepaste inventarisbronnen." + +#: awx/main/models/inventory.py:1179 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "Ongeldige %(source)s regio: %(region)s" -#: awx/main/models/inventory.py:1191 +#: awx/main/models/inventory.py:1203 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "Ongeldige filterexpressie: %(filter)s" -#: awx/main/models/inventory.py:1212 +#: awx/main/models/inventory.py:1224 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "Ongeldige groep op keuze: %(choice)s" -#: awx/main/models/inventory.py:1247 +#: awx/main/models/inventory.py:1259 msgid "Project containing inventory file used as source." msgstr "Project met inventarisbestand dat wordt gebruikt als bron." -#: awx/main/models/inventory.py:1395 +#: awx/main/models/inventory.py:1407 #, python-format msgid "" "Unable to configure this item for cloud sync. It is already managed by %s." @@ -2705,7 +2764,7 @@ msgstr "" "Kan dit item niet configureren voor cloudsynchronisatie. Wordt al beheerd " "door %s." -#: awx/main/models/inventory.py:1405 +#: awx/main/models/inventory.py:1417 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." @@ -2713,7 +2772,7 @@ msgstr "" "Het is niet toegestaan meer dan één SCM-gebaseerde inventarisbron met een " "update bovenop een projectupdate per inventaris te hebben." -#: awx/main/models/inventory.py:1412 +#: awx/main/models/inventory.py:1424 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " @@ -2723,30 +2782,30 @@ msgstr "" "ingesteld op bijwerken bij projectupdate. Configureer in plaats daarvan het " "overeenkomstige bronproject om bij te werken bij opstarten." -#: awx/main/models/inventory.py:1418 +#: awx/main/models/inventory.py:1430 msgid "SCM type sources must set `overwrite_vars` to `true`." msgstr "SCM-typebronnen moeten 'overwrite_vars' instellen op 'true'." -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1435 msgid "Cannot set source_path if not SCM type." msgstr "Kan source_path niet instellen als het geen SCM-type is." -#: awx/main/models/inventory.py:1448 +#: awx/main/models/inventory.py:1460 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "" "Inventarisbestanden uit deze projectupdate zijn gebruikt voor de " "inventarisupdate." -#: awx/main/models/inventory.py:1561 +#: awx/main/models/inventory.py:1573 msgid "Inventory script contents" msgstr "Inhoud inventarisscript" -#: awx/main/models/inventory.py:1566 +#: awx/main/models/inventory.py:1578 msgid "Organization owning this inventory script" msgstr "Organisatie die eigenaar is van deze inventarisscript" -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:66 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" @@ -2754,7 +2813,7 @@ msgstr "" "Indien ingeschakeld, worden tekstwijzigingen aangebracht in " "sjabloonbestanden op de host weergegeven in de standaardoutput" -#: awx/main/models/jobs.py:163 +#: awx/main/models/jobs.py:164 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" " at the end of a playbook run to the database and caching facts for use by " @@ -2765,37 +2824,37 @@ msgstr "" "database en worden feiten voor gebruik door Ansible in het cachegeheugen " "opgeslagen." -#: awx/main/models/jobs.py:172 +#: awx/main/models/jobs.py:173 msgid "You must provide an SSH credential." msgstr "U moet een SSH-referentie opgeven." -#: awx/main/models/jobs.py:180 +#: awx/main/models/jobs.py:181 msgid "You must provide a Vault credential." msgstr "U moet een kluisreferentie opgeven." -#: awx/main/models/jobs.py:316 +#: awx/main/models/jobs.py:317 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "Taaksjabloon moet 'inventory' verschaffen of toestaan erom te vragen." -#: awx/main/models/jobs.py:320 +#: awx/main/models/jobs.py:321 msgid "Job Template must provide 'credential' or allow prompting for it." msgstr "" "Taaksjabloon moet 'credential' verschaffen of toestaan erom te vragen." -#: awx/main/models/jobs.py:422 +#: awx/main/models/jobs.py:427 msgid "Cannot override job_type to or from a scan job." msgstr "Kan job_type niet vervangen naar of vanuit een scantaak." -#: awx/main/models/jobs.py:488 awx/main/models/projects.py:263 +#: awx/main/models/jobs.py:493 awx/main/models/projects.py:263 msgid "SCM Revision" msgstr "SCM-revisie" -#: awx/main/models/jobs.py:489 +#: awx/main/models/jobs.py:494 msgid "The SCM Revision from the Project used for this job, if available" msgstr "" "De SCM-revisie uit het project gebruikt voor deze taak, indien beschikbaar" -#: awx/main/models/jobs.py:497 +#: awx/main/models/jobs.py:502 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" @@ -2803,105 +2862,105 @@ msgstr "" "De taak SCM vernieuwen gebruik om te verzekeren dat de draaiboeken " "beschikbaar waren om de taak uit te voeren" -#: awx/main/models/jobs.py:802 +#: awx/main/models/jobs.py:809 msgid "job host summaries" msgstr "taakhostoverzichten" -#: awx/main/models/jobs.py:906 +#: awx/main/models/jobs.py:913 msgid "Host Failure" msgstr "Hostmislukking" -#: awx/main/models/jobs.py:909 awx/main/models/jobs.py:923 +#: awx/main/models/jobs.py:916 awx/main/models/jobs.py:930 msgid "No Hosts Remaining" msgstr "Geen resterende hosts" -#: awx/main/models/jobs.py:910 +#: awx/main/models/jobs.py:917 msgid "Host Polling" msgstr "Hostpolling" -#: awx/main/models/jobs.py:911 +#: awx/main/models/jobs.py:918 msgid "Host Async OK" msgstr "Host Async OK" -#: awx/main/models/jobs.py:912 +#: awx/main/models/jobs.py:919 msgid "Host Async Failure" msgstr "Host Async mislukking" -#: awx/main/models/jobs.py:913 +#: awx/main/models/jobs.py:920 msgid "Item OK" msgstr "Item OK" -#: awx/main/models/jobs.py:914 +#: awx/main/models/jobs.py:921 msgid "Item Failed" msgstr "Item mislukt" -#: awx/main/models/jobs.py:915 +#: awx/main/models/jobs.py:922 msgid "Item Skipped" msgstr "Item overgeslagen" -#: awx/main/models/jobs.py:916 +#: awx/main/models/jobs.py:923 msgid "Host Retry" msgstr "Host opnieuw proberen" -#: awx/main/models/jobs.py:918 +#: awx/main/models/jobs.py:925 msgid "File Difference" msgstr "Bestandsverschil" -#: awx/main/models/jobs.py:919 +#: awx/main/models/jobs.py:926 msgid "Playbook Started" msgstr "Draaiboek gestart" -#: awx/main/models/jobs.py:920 +#: awx/main/models/jobs.py:927 msgid "Running Handlers" msgstr "Handlers die worden uitgevoerd" -#: awx/main/models/jobs.py:921 +#: awx/main/models/jobs.py:928 msgid "Including File" msgstr "Inclusief bestand" -#: awx/main/models/jobs.py:922 +#: awx/main/models/jobs.py:929 msgid "No Hosts Matched" msgstr "Geen overeenkomende hosts" -#: awx/main/models/jobs.py:924 +#: awx/main/models/jobs.py:931 msgid "Task Started" msgstr "Taak gestart" -#: awx/main/models/jobs.py:926 +#: awx/main/models/jobs.py:933 msgid "Variables Prompted" msgstr "Variabelen gevraagd" -#: awx/main/models/jobs.py:927 +#: awx/main/models/jobs.py:934 msgid "Gathering Facts" msgstr "Feiten verzamelen" -#: awx/main/models/jobs.py:928 +#: awx/main/models/jobs.py:935 msgid "internal: on Import for Host" msgstr "intern: bij importeren voor host" -#: awx/main/models/jobs.py:929 +#: awx/main/models/jobs.py:936 msgid "internal: on Not Import for Host" msgstr "intern: niet bij importeren voor host" -#: awx/main/models/jobs.py:930 +#: awx/main/models/jobs.py:937 msgid "Play Started" msgstr "Afspelen gestart" -#: awx/main/models/jobs.py:931 +#: awx/main/models/jobs.py:938 msgid "Playbook Complete" msgstr "Draaiboek voltooid" -#: awx/main/models/jobs.py:1363 +#: awx/main/models/jobs.py:1351 msgid "Remove jobs older than a certain number of days" msgstr "Taken ouder dan een bepaald aantal dagen verwijderen" -#: awx/main/models/jobs.py:1364 +#: awx/main/models/jobs.py:1352 msgid "Remove activity stream entries older than a certain number of days" msgstr "" "Vermeldingen activiteitenstroom ouder dan een bepaald aantal dagen " "verwijderen" -#: awx/main/models/jobs.py:1365 +#: awx/main/models/jobs.py:1353 msgid "Purge and/or reduce the granularity of system tracking data" msgstr "" "Granulariteit van systeemtrackinggegevens verwijderen en/of verminderen" @@ -3324,7 +3383,7 @@ msgstr "Uitzondering bij het maken van de verbinding met Twilio: {}" msgid "Error sending notification webhook: {}" msgstr "Fout bij verzending bericht webhook: {}" -#: awx/main/scheduler/__init__.py:184 +#: awx/main/scheduler/task_manager.py:197 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" @@ -3332,7 +3391,7 @@ msgstr "" "Taak voortgebracht vanuit workflow kon niet starten omdat deze niet de " "juiste status of vereiste handmatige referenties had" -#: awx/main/scheduler/__init__.py:188 +#: awx/main/scheduler/task_manager.py:201 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" @@ -3352,7 +3411,7 @@ msgstr "De licentie voor Ansible Tower verloopt binnenkort" msgid "status_str must be either succeeded or failed" msgstr "status_str must moet geslaagd of mislukt zijn" -#: awx/main/tasks.py:1531 +#: awx/main/tasks.py:1549 msgid "Dependent inventory update {} was canceled." msgstr "De afhankelijke inventarisupdate {} is geannuleerd." @@ -3361,38 +3420,38 @@ msgstr "De afhankelijke inventarisupdate {} is geannuleerd." msgid "Unable to convert \"%s\" to boolean" msgstr "Kan \"%s\" niet converteren in boolean" -#: awx/main/utils/common.py:209 +#: awx/main/utils/common.py:235 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "Niet-ondersteund SCM-type \"%s\"" -#: awx/main/utils/common.py:216 awx/main/utils/common.py:228 -#: awx/main/utils/common.py:247 +#: awx/main/utils/common.py:242 awx/main/utils/common.py:254 +#: awx/main/utils/common.py:273 #, python-format msgid "Invalid %s URL" msgstr "Ongeldige %s URL" -#: awx/main/utils/common.py:218 awx/main/utils/common.py:257 +#: awx/main/utils/common.py:244 awx/main/utils/common.py:283 #, python-format msgid "Unsupported %s URL" msgstr "Niet-ondersteunde %s URL" -#: awx/main/utils/common.py:259 +#: awx/main/utils/common.py:285 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "Niet-ondersteunde host \"%s\" voor bestand:// URL" -#: awx/main/utils/common.py:261 +#: awx/main/utils/common.py:287 #, python-format msgid "Host is required for %s URL" msgstr "Host is vereist voor %s URL" -#: awx/main/utils/common.py:279 +#: awx/main/utils/common.py:305 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "Gebruikersnaam moet \"git\" zijn voor SSH-toegang tot %s." -#: awx/main/utils/common.py:285 +#: awx/main/utils/common.py:311 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "Gebruikersnaam moet \"hg\" zijn voor SSH-toegang tot %s." @@ -3513,287 +3572,287 @@ msgstr "Serverfout" msgid "A server error has occurred." msgstr "Er is een serverfout opgetreden" -#: awx/settings/defaults.py:664 +#: awx/settings/defaults.py:665 msgid "US East (Northern Virginia)" msgstr "VS Oosten (Northern Virginia)" -#: awx/settings/defaults.py:665 +#: awx/settings/defaults.py:666 msgid "US East (Ohio)" msgstr "VS Oosten (Ohio)" -#: awx/settings/defaults.py:666 +#: awx/settings/defaults.py:667 msgid "US West (Oregon)" msgstr "VS Westen (Oregon)" -#: awx/settings/defaults.py:667 +#: awx/settings/defaults.py:668 msgid "US West (Northern California)" msgstr "VS Westen (Northern California)" -#: awx/settings/defaults.py:668 +#: awx/settings/defaults.py:669 msgid "Canada (Central)" msgstr "Canada (Midden)" -#: awx/settings/defaults.py:669 +#: awx/settings/defaults.py:670 msgid "EU (Frankfurt)" msgstr "EU (Frankfurt)" -#: awx/settings/defaults.py:670 +#: awx/settings/defaults.py:671 msgid "EU (Ireland)" msgstr "EU (Ierland)" -#: awx/settings/defaults.py:671 +#: awx/settings/defaults.py:672 msgid "EU (London)" msgstr "EU (Londen)" -#: awx/settings/defaults.py:672 +#: awx/settings/defaults.py:673 msgid "Asia Pacific (Singapore)" msgstr "Azië-Pacific (Singapore)" -#: awx/settings/defaults.py:673 +#: awx/settings/defaults.py:674 msgid "Asia Pacific (Sydney)" msgstr "Azië-Pacific (Sydney)" -#: awx/settings/defaults.py:674 +#: awx/settings/defaults.py:675 msgid "Asia Pacific (Tokyo)" msgstr "Azië-Pacific (Tokyo)" -#: awx/settings/defaults.py:675 +#: awx/settings/defaults.py:676 msgid "Asia Pacific (Seoul)" msgstr "Azië-Pacific (Seoul)" -#: awx/settings/defaults.py:676 +#: awx/settings/defaults.py:677 msgid "Asia Pacific (Mumbai)" msgstr "Azië-Pacific (Mumbai)" -#: awx/settings/defaults.py:677 +#: awx/settings/defaults.py:678 msgid "South America (Sao Paulo)" msgstr "Zuid-Amerika (Sao Paulo)" -#: awx/settings/defaults.py:678 +#: awx/settings/defaults.py:679 msgid "US West (GovCloud)" msgstr "VS Westen (GovCloud)" -#: awx/settings/defaults.py:679 +#: awx/settings/defaults.py:680 msgid "China (Beijing)" msgstr "China (Beijing)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:729 msgid "US East 1 (B)" msgstr "VS Oosten 1 (B)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:730 msgid "US East 1 (C)" msgstr "VS Oosten 1 (C)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:731 msgid "US East 1 (D)" msgstr "VS Oosten 1 (D)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:732 msgid "US East 4 (A)" msgstr "VS Oosten 4 (A)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:733 msgid "US East 4 (B)" msgstr "VS Oosten 4 (B)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:734 msgid "US East 4 (C)" msgstr "VS Oosten 4 (C)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:735 msgid "US Central (A)" msgstr "VS Midden (A)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:736 msgid "US Central (B)" msgstr "VS Midden (B)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:737 msgid "US Central (C)" msgstr "VS Midden (C)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:738 msgid "US Central (F)" msgstr "VS Midden (F)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:739 msgid "US West (A)" msgstr "VS Westen (A)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:740 msgid "US West (B)" msgstr "VS Westen (B)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:741 msgid "US West (C)" msgstr "VS Westen (C)" -#: awx/settings/defaults.py:741 +#: awx/settings/defaults.py:742 msgid "Europe West 1 (B)" msgstr "Europa Westen 1 (B)" -#: awx/settings/defaults.py:742 +#: awx/settings/defaults.py:743 msgid "Europe West 1 (C)" msgstr "Europa Westen 1 (C)" -#: awx/settings/defaults.py:743 +#: awx/settings/defaults.py:744 msgid "Europe West 1 (D)" msgstr "Europa Westen 1 (D)" -#: awx/settings/defaults.py:744 +#: awx/settings/defaults.py:745 msgid "Europe West 2 (A)" msgstr "Europa Westen 2 (A)" -#: awx/settings/defaults.py:745 +#: awx/settings/defaults.py:746 msgid "Europe West 2 (B)" msgstr "Europa Westen 2 (B)" -#: awx/settings/defaults.py:746 +#: awx/settings/defaults.py:747 msgid "Europe West 2 (C)" msgstr "Europa Westen 2 (C)" -#: awx/settings/defaults.py:747 +#: awx/settings/defaults.py:748 msgid "Asia East (A)" msgstr "Azië Oosten (A)" -#: awx/settings/defaults.py:748 +#: awx/settings/defaults.py:749 msgid "Asia East (B)" msgstr "Azië Oosten (B)" -#: awx/settings/defaults.py:749 +#: awx/settings/defaults.py:750 msgid "Asia East (C)" msgstr "Azië Oosten (C)" -#: awx/settings/defaults.py:750 +#: awx/settings/defaults.py:751 msgid "Asia Southeast (A)" msgstr "Azië (Zuidoost) (A)" -#: awx/settings/defaults.py:751 +#: awx/settings/defaults.py:752 msgid "Asia Southeast (B)" msgstr "Azië (Zuidoost) (B)" -#: awx/settings/defaults.py:752 +#: awx/settings/defaults.py:753 msgid "Asia Northeast (A)" msgstr "Azië (Noordoost) (A)" -#: awx/settings/defaults.py:753 +#: awx/settings/defaults.py:754 msgid "Asia Northeast (B)" msgstr "Azië (Noordoost) (B)" -#: awx/settings/defaults.py:754 +#: awx/settings/defaults.py:755 msgid "Asia Northeast (C)" msgstr "Azië (Noordoost) (C)" -#: awx/settings/defaults.py:755 +#: awx/settings/defaults.py:756 msgid "Australia Southeast (A)" msgstr "Australië Zuidoost (A)" -#: awx/settings/defaults.py:756 +#: awx/settings/defaults.py:757 msgid "Australia Southeast (B)" msgstr "Australië Zuidoost (B)" -#: awx/settings/defaults.py:757 +#: awx/settings/defaults.py:758 msgid "Australia Southeast (C)" msgstr "Australië Zuidoost (C)" -#: awx/settings/defaults.py:781 +#: awx/settings/defaults.py:780 msgid "US East" msgstr "VS Oosten" -#: awx/settings/defaults.py:782 +#: awx/settings/defaults.py:781 msgid "US East 2" msgstr "VS Oosten 2" -#: awx/settings/defaults.py:783 +#: awx/settings/defaults.py:782 msgid "US Central" msgstr "VS Midden" -#: awx/settings/defaults.py:784 +#: awx/settings/defaults.py:783 msgid "US North Central" msgstr "VS Noord Midden" -#: awx/settings/defaults.py:785 +#: awx/settings/defaults.py:784 msgid "US South Central" msgstr "VS Zuid MIdden" -#: awx/settings/defaults.py:786 +#: awx/settings/defaults.py:785 msgid "US West Central" msgstr "VS West Midden" -#: awx/settings/defaults.py:787 +#: awx/settings/defaults.py:786 msgid "US West" msgstr "VS Westen" -#: awx/settings/defaults.py:788 +#: awx/settings/defaults.py:787 msgid "US West 2" msgstr "VS Westen 2" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:788 msgid "Canada East" msgstr "Canada Oosten" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:789 msgid "Canada Central" msgstr "Canada Midden" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:790 msgid "Brazil South" msgstr "Brazilië Zuiden" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:791 msgid "Europe North" msgstr "Europa Noorden" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:792 msgid "Europe West" msgstr "Europa Westen" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:793 msgid "UK West" msgstr "VK Westen" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:794 msgid "UK South" msgstr "VK Zuiden" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:795 msgid "Asia East" msgstr "Azië Oosten" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:796 msgid "Asia Southeast" msgstr "Azië Zuidoosten" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:797 msgid "Australia East" msgstr "Australië Oosten" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:798 msgid "Australia Southeast" msgstr "Australië Zuidoosten" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:799 msgid "India West" msgstr "India Westen" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:800 msgid "India South" msgstr "India Zuiden" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:801 msgid "Japan East" msgstr "Japan Oosten" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:802 msgid "Japan West" msgstr "Japan Westen" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:803 msgid "Korea Central" msgstr "Korea Midden" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:804 msgid "Korea South" msgstr "Korea Zuiden" @@ -4544,6 +4603,7 @@ msgid "SAML Team Map" msgstr "SAML-teamtoewijzing" #: awx/sso/fields.py:123 +#, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "Ongeldige verbindingsoptie(s): {invalid_options}." @@ -4560,16 +4620,19 @@ msgid "Subtree" msgstr "Substructuur" #: awx/sso/fields.py:214 +#, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "Verwachtte een lijst met drie items, maar kreeg er {length}." #: awx/sso/fields.py:215 +#, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" "Verwachtte een instantie van LDAPSearch, maar kreeg in plaats daarvan " "{input_type}." #: awx/sso/fields.py:251 +#, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." @@ -4578,20 +4641,24 @@ msgstr "" "plaats daarvan {input_type}." #: awx/sso/fields.py:289 +#, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "Ongeldig(e) gebruikerskenmerk(en): {invalid_attrs}." #: awx/sso/fields.py:306 +#, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" "Verwachtte een instantie van LDAPGroupType, maar kreeg in plaats daarvan " "{input_type}." #: awx/sso/fields.py:334 +#, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "Ongeldige gebruikersvlag: \"{invalid_flag}\"." #: awx/sso/fields.py:350 awx/sso/fields.py:517 +#, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type}" " instead." @@ -4600,46 +4667,57 @@ msgstr "" "maar kreeg in plaats daarvan {input_type}." #: awx/sso/fields.py:386 +#, python-brace-format msgid "Missing key(s): {missing_keys}." msgstr "Ontbrekende sleutel(s): {missing_keys}." #: awx/sso/fields.py:387 +#, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "Ongeldige sleutel(s): {invalid_keys}." #: awx/sso/fields.py:436 awx/sso/fields.py:553 +#, python-brace-format msgid "Invalid key(s) for organization map: {invalid_keys}." msgstr "Ongeldige sleutel(s) voor organisatietoewijzing: {invalid_keys}." #: awx/sso/fields.py:454 +#, python-brace-format msgid "Missing required key for team map: {invalid_keys}." msgstr "Ontbrekende vereiste sleutel voor teamtoewijzing: {invalid_keys}." #: awx/sso/fields.py:455 awx/sso/fields.py:572 +#, python-brace-format msgid "Invalid key(s) for team map: {invalid_keys}." msgstr "Ongeldige sleutel(s) voor teamtoewijzing: {invalid_keys}." #: awx/sso/fields.py:571 +#, python-brace-format msgid "Missing required key for team map: {missing_keys}." msgstr "Ontbrekende vereiste sleutel voor teamtoewijzing: {missing_keys}." #: awx/sso/fields.py:589 +#, python-brace-format msgid "Missing required key(s) for org info record: {missing_keys}." msgstr "Ontbrekende vereiste sleutel(s) voor org info record: {missing_keys}." #: awx/sso/fields.py:602 +#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "Ongeldige taalcode(s) voor org info: {invalid_lang_codes}." #: awx/sso/fields.py:621 +#, python-brace-format msgid "Missing required key(s) for contact: {missing_keys}." msgstr "Ontbrekende vereiste sleutel(s) voor contactpersoon: {missing_keys}." #: awx/sso/fields.py:633 +#, python-brace-format msgid "Missing required key(s) for IdP: {missing_keys}." msgstr "Ontbrekende vereiste sleutel(s) voor IdP: {missing_keys}." #: awx/sso/pipeline.py:24 +#, python-brace-format msgid "An account cannot be found for {0}" msgstr "Een account kan niet worden gevonden voor {0}" @@ -4733,6 +4811,7 @@ msgid "Make a PATCH request on the %(name)s resource" msgstr "Maak een PATCH-aanvraag voor de resource van %(name)s" #: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 +#: awx/ui/conf.py:63 msgid "UI" msgstr "Gebruikersinterface" @@ -4787,6 +4866,16 @@ msgstr "" "transparante achtergrond gebruikt. De indelingen GIF, PNG en JPEG worden " "ondersteund." +#: awx/ui/conf.py:60 +msgid "Max Job Events Retreived by UI" +msgstr "Het maximumaantal taakgebeurtenissen dat is opgehaald door de UI" + +#: awx/ui/conf.py:61 +msgid "" +"Maximum number of job events for the UI to retreive within a single request." +msgstr "" +"Het maximumaantal taakgebeurtenissen dat de UI kan ophalen met één verzoek." + #: awx/ui/fields.py:29 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " diff --git a/awx/main/access.py b/awx/main/access.py index 105b925577..563888fdcc 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -12,6 +12,7 @@ from django.db.models import Q, Prefetch from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ObjectDoesNotExist # Django REST Framework from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError @@ -31,7 +32,7 @@ from awx.conf.license import LicenseForbids, feature_enabled __all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors', 'user_accessible_objects', 'consumer_access', - 'user_admin_role', 'StateConflict',] + 'user_admin_role', 'ActiveJobConflict',] logger = logging.getLogger('awx.main.access') @@ -71,9 +72,15 @@ def get_object_from_data(field, Model, data, obj=None): raise ParseError(_("Bad data found in related field %s." % field)) -class StateConflict(ValidationError): +class ActiveJobConflict(ValidationError): status_code = 409 + def __init__(self, active_jobs): + super(ActiveJobConflict, self).__init__({ + "conflict": _("Resource is being used by running jobs."), + "active_jobs": active_jobs + }) + def register_access(model_class, access_class): access_registry[model_class] = access_class @@ -568,8 +575,7 @@ class OrganizationAccess(BaseAccess): active_jobs.extend([dict(type="inventory_update", id=o.id) for o in InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": active_jobs}) + raise ActiveJobConflict(active_jobs) return True def can_attach(self, obj, sub_obj, relationship, *args, **kwargs): @@ -662,8 +668,7 @@ class InventoryAccess(BaseAccess): active_jobs.extend([dict(type="ad_hoc_command", id=o.id) for o in AdHocCommand.objects.filter(inventory=obj, status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": active_jobs}) + raise ActiveJobConflict(active_jobs) return True def can_run_ad_hoc_commands(self, obj): @@ -788,8 +793,7 @@ class GroupAccess(BaseAccess): active_jobs.extend([dict(type="inventory_update", id=o.id) for o in InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": active_jobs}) + raise ActiveJobConflict(active_jobs) return True def can_start(self, obj, validate_license=True): @@ -839,8 +843,7 @@ class InventorySourceAccess(BaseAccess): return False active_jobs_qs = InventoryUpdate.objects.filter(inventory_source=obj, status__in=ACTIVE_STATES) if active_jobs_qs.exists(): - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": [dict(type="inventory_update", id=o.id) for o in active_jobs_qs.all()]}) + raise ActiveJobConflict([dict(type="inventory_update", id=o.id) for o in active_jobs_qs.all()]) return True @check_superuser @@ -1090,8 +1093,7 @@ class ProjectAccess(BaseAccess): active_jobs.extend([dict(type="project_update", id=o.id) for o in ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)]) if len(active_jobs) > 0: - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": active_jobs}) + raise ActiveJobConflict(active_jobs) return True @check_superuser @@ -1124,8 +1126,11 @@ class ProjectUpdateAccess(BaseAccess): def can_start(self, obj, validate_license=True): # for relaunching - if obj and obj.project: - return self.user in obj.project.update_role + try: + if obj and obj.project: + return self.user in obj.project.update_role + except ObjectDoesNotExist: + pass return False @check_superuser @@ -1265,8 +1270,7 @@ class JobTemplateAccess(BaseAccess): active_jobs = [dict(type="job", id=o.id) for o in obj.jobs.filter(status__in=ACTIVE_STATES)] if len(active_jobs) > 0: - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": active_jobs}) + raise ActiveJobConflict(active_jobs) return True @check_superuser @@ -1771,8 +1775,7 @@ class WorkflowJobTemplateAccess(BaseAccess): active_jobs = [dict(type="workflow_job", id=o.id) for o in obj.workflow_jobs.filter(status__in=ACTIVE_STATES)] if len(active_jobs) > 0: - raise StateConflict({"conflict": _("Resource is being used by running jobs"), - "active_jobs": active_jobs}) + raise ActiveJobConflict(active_jobs) return True diff --git a/awx/main/constants.py b/awx/main/constants.py index fbda8e961d..c452e19640 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -5,7 +5,7 @@ import re from django.utils.translation import ugettext_lazy as _ -CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'satellite6', 'cloudforms') +CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'cloudforms', 'tower') SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',) PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), diff --git a/awx/main/fields.py b/awx/main/fields.py index a44513ed56..777836ebf3 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -6,6 +6,7 @@ import copy import json import re import six +import urllib from jinja2 import Environment, StrictUndefined from jinja2.exceptions import UndefinedError @@ -352,6 +353,7 @@ class SmartFilterField(models.TextField): # https://docs.python.org/2/library/stdtypes.html#truth-value-testing if not value: return None + value = urllib.unquote(value) try: SmartFilter().query_from_string(value) except RuntimeError, e: diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 5b0bf5b206..8468f8e41f 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -173,6 +173,7 @@ class AnsibleInventoryLoader(object): def load(self): base_args = self.get_base_args() logger.info('Reading Ansible inventory source: %s', self.source) + data = self.command_to_json(base_args + ['--list']) # TODO: remove after we run custom scripts through ansible-inventory @@ -225,6 +226,7 @@ def load_inventory_source(source, group_filter_re=None, ''' # Sanity check: We sanitize these module names for our API but Ansible proper doesn't follow # good naming conventions + source = source.replace('rhv.py', 'ovirt4.py') source = source.replace('satellite6.py', 'foreman.py') source = source.replace('vmware.py', 'vmware_inventory.py') if not os.path.exists(source): @@ -600,27 +602,20 @@ class Command(BaseCommand): def _update_inventory(self): ''' - Update/overwrite variables from "all" group. If importing from a - cloud source attached to a specific group, variables will be set on - the base group, otherwise they will be set on the whole inventory. + Update inventory variables from "all" group. ''' - # FIXME: figure out how "all" variables are handled in the new inventory source system + # TODO: We disable variable overwrite here in case user-defined inventory variables get + # mangled. But we still need to figure out a better way of processing multiple inventory + # update variables mixing with each other. all_obj = self.inventory - all_name = 'inventory' db_variables = all_obj.variables_dict - if self.overwrite_vars: - db_variables = self.all_group.variables - else: - db_variables.update(self.all_group.variables) + db_variables.update(self.all_group.variables) if db_variables != all_obj.variables_dict: all_obj.variables = json.dumps(db_variables) all_obj.save(update_fields=['variables']) - if self.overwrite_vars: - logger.info('%s variables replaced from "all" group', all_name.capitalize()) - else: - logger.info('%s variables updated from "all" group', all_name.capitalize()) + logger.info('Inventory variables updated from "all" group') else: - logger.info('%s variables unmodified', all_name.capitalize()) + logger.info('Inventory variables unmodified') def _create_update_groups(self): ''' diff --git a/awx/main/management/commands/run_callback_receiver.py b/awx/main/management/commands/run_callback_receiver.py index 2e9969e857..1febfe58ae 100644 --- a/awx/main/management/commands/run_callback_receiver.py +++ b/awx/main/management/commands/run_callback_receiver.py @@ -3,13 +3,14 @@ # Python import logging +import os import signal +import time from uuid import UUID from multiprocessing import Process from multiprocessing import Queue as MPQueue from Queue import Empty as QueueEmpty from Queue import Full as QueueFull -import os from kombu import Connection, Exchange, Queue from kombu.mixins import ConsumerMixin @@ -18,7 +19,8 @@ from kombu.mixins import ConsumerMixin from django.conf import settings from django.core.management.base import BaseCommand from django.db import connection as django_connection -from django.db import DatabaseError +from django.db import DatabaseError, OperationalError +from django.db.utils import InterfaceError, InternalError from django.core.cache import cache as django_cache # AWX @@ -39,6 +41,9 @@ class WorkerSignalHandler: class CallbackBrokerWorker(ConsumerMixin): + + MAX_RETRIES = 2 + def __init__(self, connection, use_workers=True): self.connection = connection self.worker_queues = [] @@ -133,13 +138,40 @@ class CallbackBrokerWorker(ConsumerMixin): logger.info('Body: {}'.format( highlight(pformat(body, width=160), PythonLexer(), Terminal256Formatter(style='friendly')) )) - try: + + def _save_event_data(): if 'job_id' in body: JobEvent.create_from_data(**body) elif 'ad_hoc_command_id' in body: AdHocCommandEvent.create_from_data(**body) - except DatabaseError as e: - logger.error('Database Error Saving Job Event: {}'.format(e)) + + job_identifier = 'unknown job' + if 'job_id' in body: + job_identifier = body['job_id'] + elif 'ad_hoc_command_id' in body: + job_identifier = body['ad_hoc_command_id'] + + retries = 0 + while retries <= self.MAX_RETRIES: + try: + _save_event_data() + break + except (OperationalError, InterfaceError, InternalError) as e: + if retries >= self.MAX_RETRIES: + logger.exception('Worker could not re-establish database connectivity, shutting down gracefully: Job {}'.format(job_identifier)) + os.kill(os.getppid(), signal.SIGINT) + return + delay = 60 * retries + logger.exception('Database Error Saving Job Event, retry #{i} in {delay} seconds:'.format( + i=retries + 1, + delay=delay + )) + django_connection.close() + time.sleep(delay) + retries += 1 + except DatabaseError as e: + logger.exception('Database Error Saving Job Event for Job {}'.format(job_identifier)) + break except Exception as exc: import traceback tb = traceback.format_exc() diff --git a/awx/main/management/commands/test_isolated_connection.py b/awx/main/management/commands/test_isolated_connection.py new file mode 100644 index 0000000000..e2bfcc2f50 --- /dev/null +++ b/awx/main/management/commands/test_isolated_connection.py @@ -0,0 +1,50 @@ +import os +import shutil +import subprocess +import sys +import tempfile +from optparse import make_option + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError + +from awx.main.expect import run + + +class Command(BaseCommand): + """Tests SSH connectivity between a controller and target isolated node""" + help = 'Tests SSH connectivity between a controller and target isolated node' + + option_list = BaseCommand.option_list + ( + make_option('--hostname', dest='hostname', type='string', + help='Hostname of an isolated node'), + ) + + def handle(self, *args, **options): + hostname = options.get('hostname') + if not hostname: + raise CommandError("--hostname is a required argument") + + try: + path = tempfile.mkdtemp(prefix='awx_isolated_ssh', dir=settings.AWX_PROOT_BASE_PATH) + args = [ + 'ansible', 'all', '-i', '{},'.format(hostname), '-u', + settings.AWX_ISOLATED_USERNAME, '-T5', '-m', 'shell', + '-a', 'hostname', '-vvv' + ] + if all([ + getattr(settings, 'AWX_ISOLATED_KEY_GENERATION', False) is True, + getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None) + ]): + ssh_key_path = os.path.join(path, '.isolated') + ssh_auth_sock = os.path.join(path, 'ssh_auth.sock') + run.open_fifo_write(ssh_key_path, settings.AWX_ISOLATED_PRIVATE_KEY) + args = run.wrap_args_with_ssh_agent(args, ssh_key_path, ssh_auth_sock) + try: + print ' '.join(args) + subprocess.check_call(args) + except subprocess.CalledProcessError as e: + sys.exit(e.returncode) + finally: + shutil.rmtree(path) + diff --git a/awx/main/managers.py b/awx/main/managers.py index 4825b33231..69d4a24b6b 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -7,7 +7,7 @@ import logging from django.db import models from django.utils.timezone import now -from django.db.models import Sum +from django.db.models import Sum, Q from django.conf import settings from awx.main.utils.filters import SmartFilter @@ -21,9 +21,9 @@ class HostManager(models.Manager): """Custom manager class for Hosts model.""" def active_count(self): - """Return count of active, unique hosts for licensing.""" + """Return count of active, unique hosts for licensing. Exclude ones source from another Tower""" try: - return self.order_by('name').distinct('name').count() + return self.filter(~Q(inventory_sources__source='tower')).order_by('name').distinct('name').count() except NotImplementedError: # For unit tests only, SQLite doesn't support distinct('name') return len(set(self.values_list('name', flat=True))) diff --git a/awx/main/middleware.py b/awx/main/middleware.py index 2cae0961c3..3221ee27ea 100644 --- a/awx/main/middleware.py +++ b/awx/main/middleware.py @@ -5,6 +5,10 @@ import logging import threading import uuid import six +import time +import cProfile +import pstats +import os from django.conf import settings from django.contrib.auth.models import User @@ -25,6 +29,38 @@ from awx.conf import fields, register logger = logging.getLogger('awx.main.middleware') analytics_logger = logging.getLogger('awx.analytics.activity_stream') +perf_logger = logging.getLogger('awx.analytics.performance') + + +class TimingMiddleware(threading.local): + + dest = '/var/lib/awx/profile' + + def process_request(self, request): + self.start_time = time.time() + if settings.AWX_REQUEST_PROFILE: + self.prof = cProfile.Profile() + self.prof.enable() + + def process_response(self, request, response): + total_time = time.time() - self.start_time + response['X-API-Total-Time'] = '%0.3fs' % total_time + if settings.AWX_REQUEST_PROFILE: + self.prof.disable() + cprofile_file = self.save_profile_file(request) + response['cprofile_file'] = cprofile_file + perf_logger.info('api response times', extra=dict(python_objects=dict(request=request, response=response))) + return response + + def save_profile_file(self, request): + if not os.path.isdir(self.dest): + os.makedirs(self.dest) + filename = '%.3fs-%s' % (pstats.Stats(self.prof).total_tt, uuid.uuid4()) + filepath = os.path.join(self.dest, filename) + with open(filepath, 'w') as f: + f.write('%s %s\n' % (request.method, request.get_full_path())) + pstats.Stats(self.prof, stream=f).sort_stats('cumulative').print_stats() + return filepath class ActivityStreamMiddleware(threading.local): diff --git a/awx/main/migrations/0003_squashed_v300_v303_updates.py b/awx/main/migrations/0003_squashed_v300_v303_updates.py index 82d781ec85..48210bfa48 100644 --- a/awx/main/migrations/0003_squashed_v300_v303_updates.py +++ b/awx/main/migrations/0003_squashed_v300_v303_updates.py @@ -8,14 +8,9 @@ from __future__ import unicode_literals from django.db import migrations, models from django.conf import settings import awx.main.fields -import jsonfield.fields - -def update_dashed_host_variables(apps, schema_editor): - Host = apps.get_model('main', 'Host') - for host in Host.objects.filter(variables='---'): - host.variables = '' - host.save() +import _squashed +from _squashed_30 import SQUASHED_30 class Migration(migrations.Migration): @@ -27,13 +22,7 @@ class Migration(migrations.Migration): (b'main', '0025_v300_update_rbac_parents'), (b'main', '0026_v300_credential_unique'), (b'main', '0027_v300_team_migrations'), - (b'main', '0028_v300_org_team_cascade'), - (b'main', '0029_v302_add_ask_skip_tags'), - (b'main', '0030_v302_job_survey_passwords'), - (b'main', '0031_v302_migrate_survey_passwords'), - (b'main', '0032_v302_credential_permissions_update'), - (b'main', '0033_v303_v245_host_variable_fix'),] - + (b'main', '0028_v300_org_team_cascade')] + _squashed.replaces(SQUASHED_30, applied=True) dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), @@ -130,27 +119,4 @@ class Migration(migrations.Migration): field=models.ForeignKey(related_name='teams', to='main.Organization'), preserve_default=False, ), - # add ask skip tags - migrations.AddField( - model_name='jobtemplate', - name='ask_skip_tags_on_launch', - field=models.BooleanField(default=False), - ), - # job survery passwords - migrations.AddField( - model_name='job', - name='survey_passwords', - field=jsonfield.fields.JSONField(default={}, editable=False, blank=True), - ), - # RBAC credential permission updates - migrations.AlterField( - model_name='credential', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_administrator', b'organization.admin_role'], to='main.Role', null=b'True'), - ), - migrations.AlterField( - model_name='credential', - name='use_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), - ), - ] + ] + _squashed.operations(SQUASHED_30, applied=True) diff --git a/awx/main/migrations/0004_squashed_v310_release.py b/awx/main/migrations/0004_squashed_v310_release.py index e4b1df2290..f4263feaa3 100644 --- a/awx/main/migrations/0004_squashed_v310_release.py +++ b/awx/main/migrations/0004_squashed_v310_release.py @@ -8,6 +8,9 @@ import django.db.models.deletion import awx.main.models.workflow import awx.main.fields +import _squashed +from _squashed_30 import SQUASHED_30 + class Migration(migrations.Migration): @@ -15,11 +18,11 @@ class Migration(migrations.Migration): ('main', '0003_squashed_v300_v303_updates'), ] - replaces = [ + replaces = _squashed.replaces(SQUASHED_30) + [ (b'main', '0034_v310_release'), ] - operations = [ + operations = _squashed.operations(SQUASHED_30) + [ # Create ChannelGroup table migrations.CreateModel( name='ChannelGroup', diff --git a/awx/main/migrations/0005_squashed_v310_v313_updates.py b/awx/main/migrations/0005_squashed_v310_v313_updates.py index 85dac8bbaa..3a2e05e270 100644 --- a/awx/main/migrations/0005_squashed_v310_v313_updates.py +++ b/awx/main/migrations/0005_squashed_v310_v313_updates.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from django.db import migrations -from django.db import migrations, models +import _squashed +from _squashed_31 import SQUASHED_31 class Migration(migrations.Migration): @@ -10,28 +12,5 @@ class Migration(migrations.Migration): ('main', '0004_squashed_v310_release'), ] - replaces = [ - (b'main', '0035_v310_remove_tower_settings'), - ] - - operations = [ - # Remove Tower settings, these settings are now in separate awx.conf app. - migrations.RemoveField( - model_name='towersettings', - name='user', - ), - migrations.DeleteModel( - name='TowerSettings', - ), - - migrations.AlterField( - model_name='project', - name='scm_type', - field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), - ), - migrations.AlterField( - model_name='projectupdate', - name='scm_type', - field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), - ), - ] + replaces = _squashed.replaces(SQUASHED_31) + operations = _squashed.operations(SQUASHED_31) diff --git a/awx/main/migrations/0005a_squashed_v310_v313_updates.py b/awx/main/migrations/0005a_squashed_v310_v313_updates.py deleted file mode 100644 index 6599268ce1..0000000000 --- a/awx/main/migrations/0005a_squashed_v310_v313_updates.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0005_squashed_v310_v313_updates'), - ] - - replaces = [ - (b'main', '0036_v311_insights'), - ] - - operations = [ - migrations.AlterField( - model_name='project', - name='scm_type', - field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), - ), - migrations.AlterField( - model_name='projectupdate', - name='scm_type', - field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), - ), - ] \ No newline at end of file diff --git a/awx/main/migrations/0005b_squashed_v310_v313_updates.py b/awx/main/migrations/0005b_squashed_v310_v313_updates.py deleted file mode 100644 index ea9d26d2bc..0000000000 --- a/awx/main/migrations/0005b_squashed_v310_v313_updates.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0005a_squashed_v310_v313_updates'), - ] - - replaces = [ - (b'main', '0037_v313_instance_version'), - ] - - operations = [ - # Remove Tower settings, these settings are now in separate awx.conf app. - migrations.AddField( - model_name='instance', - name='version', - field=models.CharField(max_length=24, blank=True), - ), - ] diff --git a/awx/main/migrations/0006_v320_release.py b/awx/main/migrations/0006_v320_release.py index 4cb87f4fba..c0a4330e04 100644 --- a/awx/main/migrations/0006_v320_release.py +++ b/awx/main/migrations/0006_v320_release.py @@ -6,7 +6,13 @@ from __future__ import unicode_literals from psycopg2.extensions import AsIs # Django -from django.db import migrations, models +from django.db import ( + connection, + migrations, + models, + OperationalError, + ProgrammingError +) from django.conf import settings import taggit.managers @@ -15,12 +21,24 @@ import awx.main.fields from awx.main.models import Host +def replaces(): + squashed = ['0005a_squashed_v310_v313_updates', '0005b_squashed_v310_v313_updates'] + try: + recorder = migrations.recorder.MigrationRecorder(connection) + result = recorder.migration_qs.filter(app='main').filter(name__in=squashed).all() + return [('main', m.name) for m in result] + except (OperationalError, ProgrammingError): + return [] + + class Migration(migrations.Migration): dependencies = [ - ('main', '0005b_squashed_v310_v313_updates'), + ('main', '0005_squashed_v310_v313_updates'), ] + replaces = replaces() + operations = [ # Release UJT unique_together constraint migrations.AlterUniqueTogether( diff --git a/awx/main/migrations/0007_v320_data_migrations.py b/awx/main/migrations/0007_v320_data_migrations.py index 9461e81bcb..e8ede86ba9 100644 --- a/awx/main/migrations/0007_v320_data_migrations.py +++ b/awx/main/migrations/0007_v320_data_migrations.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals from django.db import migrations, models # AWX +from awx.main.migrations import ActivityStreamDisabledMigration from awx.main.migrations import _inventory_source as invsrc from awx.main.migrations import _migration_utils as migration_utils from awx.main.migrations import _reencrypt as reencrypt @@ -15,7 +16,7 @@ from awx.main.migrations import _azure_credentials as azurecreds import awx.main.fields -class Migration(migrations.Migration): +class Migration(ActivityStreamDisabledMigration): dependencies = [ ('main', '0006_v320_release'), diff --git a/awx/main/migrations/0009_v322_add_setting_field_for_activity_stream.py b/awx/main/migrations/0009_v322_add_setting_field_for_activity_stream.py new file mode 100644 index 0000000000..3d69de2b33 --- /dev/null +++ b/awx/main/migrations/0009_v322_add_setting_field_for_activity_stream.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +import awx.main.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0008_v320_drop_v1_credential_fields'), + ] + + operations = [ + migrations.AddField( + model_name='activitystream', + name='setting', + field=awx.main.fields.JSONField(default=dict, blank=True), + ), + ] diff --git a/awx/main/migrations/0010_v322_add_ovirt4_tower_inventory.py b/awx/main/migrations/0010_v322_add_ovirt4_tower_inventory.py new file mode 100644 index 0000000000..aac423cd1c --- /dev/null +++ b/awx/main/migrations/0010_v322_add_ovirt4_tower_inventory.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +# AWX +from awx.main.migrations import _credentialtypes as credentialtypes + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0009_v322_add_setting_field_for_activity_stream'), + ] + + operations = [ + migrations.RunPython(credentialtypes.create_rhv_tower_credtype), + migrations.AlterField( + model_name='inventorysource', + name='source', + field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a Project'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'rhv', 'Red Hat Virtualization'), (b'tower', 'Ansible Tower'), (b'custom', 'Custom Script')]), + ), + migrations.AlterField( + model_name='inventoryupdate', + name='source', + field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'File, Directory or Script'), (b'scm', 'Sourced from a Project'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'satellite6', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'rhv', 'Red Hat Virtualization'), (b'tower', 'Ansible Tower'), (b'custom', 'Custom Script')]), + ), + ] diff --git a/awx/main/migrations/0011_v322_encrypt_survey_passwords.py b/awx/main/migrations/0011_v322_encrypt_survey_passwords.py new file mode 100644 index 0000000000..0d797ae5e1 --- /dev/null +++ b/awx/main/migrations/0011_v322_encrypt_survey_passwords.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +from awx.main.migrations import ActivityStreamDisabledMigration +from awx.main.migrations import _reencrypt as reencrypt +from awx.main.migrations import _migration_utils as migration_utils + + +class Migration(ActivityStreamDisabledMigration): + + dependencies = [ + ('main', '0010_v322_add_ovirt4_tower_inventory'), + ] + + operations = [ + migrations.RunPython(migration_utils.set_current_apps_for_migrations), + migrations.RunPython(reencrypt.encrypt_survey_passwords), + ] diff --git a/awx/main/migrations/0012_v322_update_cred_types.py b/awx/main/migrations/0012_v322_update_cred_types.py new file mode 100644 index 0000000000..86d9fd55fa --- /dev/null +++ b/awx/main/migrations/0012_v322_update_cred_types.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +# AWX +from awx.main.migrations import _credentialtypes as credentialtypes + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0011_v322_encrypt_survey_passwords'), + ] + + operations = [ + migrations.RunPython(credentialtypes.add_azure_cloud_environment_field), + ] diff --git a/awx/main/migrations/0009_v330_multi_credential.py b/awx/main/migrations/0013_v330_multi_credential.py similarity index 96% rename from awx/main/migrations/0009_v330_multi_credential.py rename to awx/main/migrations/0013_v330_multi_credential.py index 69c602a3e0..abc186c382 100644 --- a/awx/main/migrations/0009_v330_multi_credential.py +++ b/awx/main/migrations/0013_v330_multi_credential.py @@ -11,7 +11,7 @@ from awx.main.migrations._multi_cred import migrate_to_multi_cred class Migration(migrations.Migration): dependencies = [ - ('main', '0008_v320_drop_v1_credential_fields'), + ('main', '0012_v322_update_cred_types'), ] operations = [ diff --git a/awx/main/migrations/0010_saved_launchtime_configs.py b/awx/main/migrations/0014_v330_saved_launchtime_configs.py similarity index 99% rename from awx/main/migrations/0010_saved_launchtime_configs.py rename to awx/main/migrations/0014_v330_saved_launchtime_configs.py index 1ef2c5087c..fbd26eec1b 100644 --- a/awx/main/migrations/0010_saved_launchtime_configs.py +++ b/awx/main/migrations/0014_v330_saved_launchtime_configs.py @@ -13,7 +13,7 @@ from awx.main.migrations._scan_jobs import remove_scan_type_nodes class Migration(migrations.Migration): dependencies = [ - ('main', '0009_v330_multi_credential'), + ('main', '0013_v330_multi_credential'), ] operations = [ diff --git a/awx/main/migrations/0011_blank_start_args.py b/awx/main/migrations/0015_v330_blank_start_args.py similarity index 90% rename from awx/main/migrations/0011_blank_start_args.py rename to awx/main/migrations/0015_v330_blank_start_args.py index ba3648a31e..2dc57c1593 100644 --- a/awx/main/migrations/0011_blank_start_args.py +++ b/awx/main/migrations/0015_v330_blank_start_args.py @@ -13,7 +13,7 @@ from awx.main.migrations._reencrypt import blank_old_start_args class Migration(migrations.Migration): dependencies = [ - ('main', '0010_saved_launchtime_configs'), + ('main', '0014_v330_saved_launchtime_configs'), ] operations = [ diff --git a/awx/main/migrations/0012_non_blank_workflow.py b/awx/main/migrations/0016_v330_non_blank_workflow.py similarity index 93% rename from awx/main/migrations/0012_non_blank_workflow.py rename to awx/main/migrations/0016_v330_non_blank_workflow.py index b863b8460f..1b3880f8d4 100644 --- a/awx/main/migrations/0012_non_blank_workflow.py +++ b/awx/main/migrations/0016_v330_non_blank_workflow.py @@ -10,7 +10,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('main', '0011_blank_start_args'), + ('main', '0015_v330_blank_start_args'), ] operations = [ diff --git a/awx/main/migrations/0013_move_deprecated_stdout.py b/awx/main/migrations/0017_v330_move_deprecated_stdout.py similarity index 97% rename from awx/main/migrations/0013_move_deprecated_stdout.py rename to awx/main/migrations/0017_v330_move_deprecated_stdout.py index f2c6250649..5b7f8fc027 100644 --- a/awx/main/migrations/0013_move_deprecated_stdout.py +++ b/awx/main/migrations/0017_v330_move_deprecated_stdout.py @@ -8,7 +8,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('main', '0012_non_blank_workflow'), + ('main', '0016_v330_non_blank_workflow'), ] operations = [ diff --git a/awx/main/migrations/__init__.py b/awx/main/migrations/__init__.py index 709b95a6a6..2ea54e7880 100644 --- a/awx/main/migrations/__init__.py +++ b/awx/main/migrations/__init__.py @@ -1,2 +1,12 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. + +from django.db.migrations import Migration + + +class ActivityStreamDisabledMigration(Migration): + + def apply(self, project_state, schema_editor, collect_sql=False): + from awx.main.signals import disable_activity_stream + with disable_activity_stream(): + return Migration.apply(self, project_state, schema_editor, collect_sql) diff --git a/awx/main/migrations/_credentialtypes.py b/awx/main/migrations/_credentialtypes.py index f9f0c8eab0..0b49afb3b6 100644 --- a/awx/main/migrations/_credentialtypes.py +++ b/awx/main/migrations/_credentialtypes.py @@ -178,3 +178,14 @@ def add_vault_id_field(apps, schema_editor): vault_credtype = CredentialType.objects.get(kind='vault') vault_credtype.inputs = CredentialType.defaults.get('vault')().inputs vault_credtype.save() + + +def create_rhv_tower_credtype(apps, schema_editor): + CredentialType.setup_tower_managed_defaults() + + +def add_azure_cloud_environment_field(apps, schema_editor): + azure_rm_credtype = CredentialType.objects.get(kind='cloud', + name='Microsoft Azure Resource Manager') + azure_rm_credtype.inputs = CredentialType.defaults.get('azure_rm')().inputs + azure_rm_credtype.save() diff --git a/awx/main/migrations/_inventory_source.py b/awx/main/migrations/_inventory_source.py index e2401ee3ae..7534c53fca 100644 --- a/awx/main/migrations/_inventory_source.py +++ b/awx/main/migrations/_inventory_source.py @@ -1,6 +1,7 @@ import logging from django.db.models import Q +import six logger = logging.getLogger('awx.main.migrations') @@ -38,8 +39,10 @@ def rename_inventory_sources(apps, schema_editor): Q(deprecated_group__inventory__organization=org)).distinct().all()): inventory = invsrc.deprecated_group.inventory if invsrc.deprecated_group else invsrc.inventory - name = '{0} - {1} - {2}'.format(invsrc.name, inventory.name, i) - logger.debug("Renaming InventorySource({0}) {1} -> {2}".format(invsrc.pk, invsrc.name, name)) + name = six.text_type('{0} - {1} - {2}').format(invsrc.name, inventory.name, i) + logger.debug(six.text_type("Renaming InventorySource({0}) {1} -> {2}").format( + invsrc.pk, invsrc.name, name + )) invsrc.name = name invsrc.save() diff --git a/awx/main/migrations/_reencrypt.py b/awx/main/migrations/_reencrypt.py index 6a293a5e3c..ceeea2a33e 100644 --- a/awx/main/migrations/_reencrypt.py +++ b/awx/main/migrations/_reencrypt.py @@ -1,5 +1,7 @@ import logging +import json from django.utils.translation import ugettext_lazy as _ +import six from awx.conf.migrations._reencrypt import ( decrypt_field, @@ -65,7 +67,6 @@ def _credentials(apps): credential.save() - def _unified_jobs(apps): UnifiedJob = apps.get_model('main', 'UnifiedJob') for uj in UnifiedJob.objects.all(): @@ -91,3 +92,53 @@ def blank_old_start_args(apps, schema_editor): logger.debug('Blanking job args for %s', uj.pk) uj.start_args = '' uj.save() + + +def encrypt_survey_passwords(apps, schema_editor): + _encrypt_survey_passwords( + apps.get_model('main', 'Job'), + apps.get_model('main', 'JobTemplate'), + apps.get_model('main', 'WorkflowJob'), + apps.get_model('main', 'WorkflowJobTemplate'), + ) + + +def _encrypt_survey_passwords(Job, JobTemplate, WorkflowJob, WorkflowJobTemplate): + from awx.main.utils.encryption import encrypt_value + for _type in (JobTemplate, WorkflowJobTemplate): + for jt in _type.objects.exclude(survey_spec={}): + changed = False + if jt.survey_spec.get('spec', []): + for field in jt.survey_spec['spec']: + if field.get('type') == 'password' and field.get('default', ''): + default = field['default'] + if default.startswith('$encrypted$'): + if default == '$encrypted$': + # If you have a survey_spec with a literal + # '$encrypted$' as the default, you have + # encountered a known bug in awx/Tower + # https://github.com/ansible/ansible-tower/issues/7800 + logger.error( + '{}.pk={} survey_spec has ambiguous $encrypted$ default for {}, needs attention...'.format(jt, jt.pk, field['variable']) + ) + field['default'] = '' + changed = True + continue + field['default'] = encrypt_value(field['default'], pk=None) + changed = True + if changed: + jt.save() + + for _type in (Job, WorkflowJob): + for job in _type.objects.defer('result_stdout_text').exclude(survey_passwords={}).iterator(): + changed = False + for key in job.survey_passwords: + if key in job.extra_vars: + extra_vars = json.loads(job.extra_vars) + if not extra_vars.get(key, '') or extra_vars[key].startswith('$encrypted$'): + continue + extra_vars[key] = encrypt_value(extra_vars[key], pk=None) + job.extra_vars = json.dumps(extra_vars) + changed = True + if changed: + job.save() diff --git a/awx/main/migrations/_squashed.py b/awx/main/migrations/_squashed.py new file mode 100644 index 0000000000..e2f19970b4 --- /dev/null +++ b/awx/main/migrations/_squashed.py @@ -0,0 +1,63 @@ +from itertools import chain +from django.db import ( + connection, + migrations, + OperationalError, + ProgrammingError, +) + + +def squash_data(squashed): + '''Returns a tuple of the squashed_keys and the key position to begin + processing replace and operation lists''' + + cm = current_migration() + squashed_keys = sorted(squashed.keys()) + if cm is None: + return squashed_keys, 0 + + try: + key_index = squashed_keys.index(cm.name) + 1 + except ValueError: + key_index = 0 + return squashed_keys, key_index + + +def current_migration(exclude_squashed=True): + '''Get the latest migration non-squashed migration''' + try: + recorder = migrations.recorder.MigrationRecorder(connection) + migration_qs = recorder.migration_qs.filter(app='main') + if exclude_squashed: + migration_qs = migration_qs.exclude(name__contains='squashed') + return migration_qs.latest('id') + except (recorder.Migration.DoesNotExist, OperationalError, ProgrammingError): + return None + + +def replaces(squashed, applied=False): + '''Build a list of replacement migrations based on the most recent non-squashed migration + and the provided list of SQUASHED migrations. If the most recent non-squashed migration + is not present anywhere in the SQUASHED dictionary, assume they have all been applied. + + If applied is True, this will return a list of all the migrations that have already + been applied. + ''' + squashed_keys, key_index = squash_data(squashed) + if applied: + return [(b'main', key) for key in squashed_keys[:key_index]] + return [(b'main', key) for key in squashed_keys[key_index:]] + + +def operations(squashed, applied=False): + '''Build a list of migration operations based on the most recent non-squashed migration + and the provided list of squashed migrations. If the most recent non-squashed migration + is not present anywhere in the `squashed` dictionary, assume they have all been applied. + + If applied is True, this will return a list of all the operations that have + already been applied. + ''' + squashed_keys, key_index = squash_data(squashed) + op_keys = squashed_keys[:key_index] if applied else squashed_keys[key_index:] + ops = [squashed[op_key] for op_key in op_keys] + return [op for op in chain.from_iterable(ops)] diff --git a/awx/main/migrations/_squashed_30.py b/awx/main/migrations/_squashed_30.py new file mode 100644 index 0000000000..5dd99762d6 --- /dev/null +++ b/awx/main/migrations/_squashed_30.py @@ -0,0 +1,60 @@ +from django.db import ( + migrations, + models, +) +import jsonfield.fields +import awx.main.fields + +from awx.main.migrations import _save_password_keys +from awx.main.migrations import _migration_utils as migration_utils + + +def update_dashed_host_variables(apps, schema_editor): + Host = apps.get_model('main', 'Host') + for host in Host.objects.filter(variables='---'): + host.variables = '' + host.save() + + +SQUASHED_30 = { + '0029_v302_add_ask_skip_tags': [ + # add ask skip tags + migrations.AddField( + model_name='jobtemplate', + name='ask_skip_tags_on_launch', + field=models.BooleanField(default=False), + ), + ], + '0030_v302_job_survey_passwords': [ + # job survery passwords + migrations.AddField( + model_name='job', + name='survey_passwords', + field=jsonfield.fields.JSONField(default={}, editable=False, blank=True), + ), + ], + '0031_v302_migrate_survey_passwords': [ + migrations.RunPython(migration_utils.set_current_apps_for_migrations), + migrations.RunPython(_save_password_keys.migrate_survey_passwords), + ], + '0032_v302_credential_permissions_update': [ + # RBAC credential permission updates + migrations.AlterField( + model_name='credential', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_administrator', b'organization.admin_role'], to='main.Role', null=b'True'), + ), + migrations.AlterField( + model_name='credential', + name='use_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + ), + ], + '0033_v303_v245_host_variable_fix': [ + migrations.RunPython(migration_utils.set_current_apps_for_migrations), + migrations.RunPython(update_dashed_host_variables), + ], +} + + +__all__ = ['SQUASHED_30'] diff --git a/awx/main/migrations/_squashed_31.py b/awx/main/migrations/_squashed_31.py new file mode 100644 index 0000000000..3c43c26a48 --- /dev/null +++ b/awx/main/migrations/_squashed_31.py @@ -0,0 +1,50 @@ +from django.db import ( + migrations, + models, +) + +SQUASHED_31 = { + '0035_v310_remove_tower_settings': [ + # Remove Tower settings, these settings are now in separate awx.conf app. + migrations.RemoveField( + model_name='towersettings', + name='user', + ), + migrations.DeleteModel( + name='TowerSettings', + ), + + migrations.AlterField( + model_name='project', + name='scm_type', + field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), + ), + migrations.AlterField( + model_name='projectupdate', + name='scm_type', + field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), + ), + ], + '0036_v311_insights': [ + migrations.AlterField( + model_name='project', + name='scm_type', + field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), + ), + migrations.AlterField( + model_name='projectupdate', + name='scm_type', + field=models.CharField(default=b'', choices=[(b'', 'Manual'), (b'git', 'Git'), (b'hg', 'Mercurial'), (b'svn', 'Subversion'), (b'insights', 'Red Hat Insights')], max_length=8, blank=True, help_text='Specifies the source control system used to store the project.', verbose_name='SCM Type'), + ), + ], + '0037_v313_instance_version': [ + # Remove Tower settings, these settings are now in separate awx.conf app. + migrations.AddField( + model_name='instance', + name='version', + field=models.CharField(max_length=24, blank=True), + ), + ], +} + +__all__ = ['SQUASHED_31'] diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index 4d8a4e9709..94df2f985c 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -3,6 +3,7 @@ # Tower from awx.api.versioning import reverse +from awx.main.fields import JSONField # Django from django.db import models @@ -66,6 +67,8 @@ class ActivityStream(models.Model): role = models.ManyToManyField("Role", blank=True) instance_group = models.ManyToManyField("InstanceGroup", blank=True) + setting = JSONField(blank=True) + def get_absolute_url(self, request=None): return reverse('api:activity_stream_detail', kwargs={'pk': self.pk}, request=request) diff --git a/awx/main/models/base.py b/awx/main/models/base.py index 173f324420..08c9562806 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -50,7 +50,7 @@ PROJECT_UPDATE_JOB_TYPE_CHOICES = [ (PERM_INVENTORY_CHECK, _('Check')), ] -CLOUD_INVENTORY_SOURCES = ['ec2', 'vmware', 'gce', 'azure_rm', 'openstack', 'custom', 'satellite6', 'cloudforms', 'scm',] +CLOUD_INVENTORY_SOURCES = ['ec2', 'vmware', 'gce', 'azure_rm', 'openstack', 'rhv', 'custom', 'satellite6', 'cloudforms', 'scm', 'tower',] VERBOSITY_CHOICES = [ (0, '0 (Normal)'), @@ -288,7 +288,10 @@ class PrimordialModel(CreatedModifiedModel): continue if not (self.pk and self.pk == obj.pk): errors.append( - '%s with this (%s) combination already exists.' % (model.__name__, ', '.join(ut)) + '%s with this (%s) combination already exists.' % ( + model.__name__, + ', '.join(set(ut) - {'polymorphic_ctype'}) + ) ) if errors: raise ValidationError(errors) diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index 9d7363c095..aecd195746 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -3,6 +3,7 @@ from collections import OrderedDict import functools import json +import logging import operator import os import stat @@ -35,6 +36,8 @@ from awx.main.utils import encrypt_field __all__ = ['Credential', 'CredentialType', 'V1Credential'] +logger = logging.getLogger('awx.main.models.credential') + class V1Credential(object): @@ -59,7 +62,9 @@ class V1Credential(object): ('gce', 'Google Compute Engine'), ('azure_rm', 'Microsoft Azure Resource Manager'), ('openstack', 'OpenStack'), + ('rhv', 'Red Hat Virtualization'), ('insights', 'Insights'), + ('tower', 'Ansible Tower'), ] FIELDS = { 'kind': models.CharField( @@ -413,8 +418,8 @@ class CredentialType(CommonModelNameNotUnique): ENV_BLACKLIST = set(( 'VIRTUAL_ENV', 'PATH', 'PYTHONPATH', 'PROOT_TMP_DIR', 'JOB_ID', 'INVENTORY_ID', 'INVENTORY_SOURCE_ID', 'INVENTORY_UPDATE_ID', - 'AD_HOC_COMMAND_ID', 'REST_API_URL', 'REST_API_TOKEN', 'TOWER_HOST', - 'AWX_HOST', 'MAX_EVENT_RES', 'CALLBACK_QUEUE', 'CALLBACK_CONNECTION', 'CACHE', + 'AD_HOC_COMMAND_ID', 'REST_API_URL', 'REST_API_TOKEN', 'MAX_EVENT_RES', + 'CALLBACK_QUEUE', 'CALLBACK_CONNECTION', 'CACHE', 'JOB_CALLBACK_DEBUG', 'INVENTORY_HOSTVARS', 'FACT_QUEUE', )) @@ -498,6 +503,11 @@ class CredentialType(CommonModelNameNotUnique): for default in cls.defaults.values(): default_ = default() if persisted: + if CredentialType.objects.filter(name=default_.name, kind=default_.kind).count(): + continue + logger.debug(_( + "adding %s credential type" % default_.name + )) default_.save() @classmethod @@ -1009,6 +1019,12 @@ def azure_rm(cls): 'id': 'tenant', 'label': 'Tenant ID', 'type': 'string' + }, { + 'id': 'cloud_environment', + 'label': 'Azure Cloud Environment', + 'type': 'string', + 'help_text': ('Environment variable AZURE_CLOUD_ENVIRONMENT when' + ' using Azure GovCloud or Azure stack.') }], 'required': ['subscription'], } @@ -1041,3 +1057,89 @@ def insights(cls): }, }, ) + + +@CredentialType.default +def rhv(cls): + return cls( + kind='cloud', + name='Red Hat Virtualization', + managed_by_tower=True, + inputs={ + 'fields': [{ + 'id': 'host', + 'label': 'Host (Authentication URL)', + 'type': 'string', + 'help_text': ('The host to authenticate with.') + }, { + 'id': 'username', + 'label': 'Username', + 'type': 'string' + }, { + 'id': 'password', + 'label': 'Password', + 'type': 'string', + 'secret': True, + }, { + 'id': 'ca_file', + 'label': 'CA File', + 'type': 'string', + 'help_text': ('Absolute file path to the CA file to use (optional)') + }], + 'required': ['host', 'username', 'password'], + }, + injectors={ + # The duplication here is intentional; the ovirt4 inventory plugin + # writes a .ini file for authentication, while the ansible modules for + # ovirt4 use a separate authentication process that support + # environment variables; by injecting both, we support both + 'file': { + 'template': '\n'.join([ + '[ovirt]', + 'ovirt_url={{host}}', + 'ovirt_username={{username}}', + 'ovirt_password={{password}}', + '{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}']) + }, + 'env': { + 'OVIRT_INI_PATH': '{{tower.filename}}', + 'OVIRT_URL': '{{host}}', + 'OVIRT_USERNAME': '{{username}}', + 'OVIRT_PASSWORD': '{{password}}' + } + }, + ) + + +@CredentialType.default +def tower(cls): + return cls( + kind='cloud', + name='Ansible Tower', + managed_by_tower=True, + inputs={ + 'fields': [{ + 'id': 'host', + 'label': 'Ansible Tower Hostname', + 'type': 'string', + 'help_text': ('The Ansible Tower base URL to authenticate with.') + }, { + 'id': 'username', + 'label': 'Username', + 'type': 'string' + }, { + 'id': 'password', + 'label': 'Password', + 'type': 'string', + 'secret': True, + }], + 'required': ['host', 'username', 'password'], + }, + injectors={ + 'env': { + 'TOWER_HOST': '{{host}}', + 'TOWER_USERNAME': '{{username}}', + 'TOWER_PASSWORD': '{{password}}', + } + }, + ) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index bfae359f09..1b04548757 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -399,8 +399,13 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): active_hosts = self.hosts failed_hosts = active_hosts.filter(has_active_failures=True) active_groups = self.groups + if self.kind == 'smart': + active_groups = active_groups.none() failed_groups = active_groups.filter(has_active_failures=True) - active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) + if self.kind == 'smart': + active_inventory_sources = self.inventory_sources.none() + else: + active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) failed_inventory_sources = active_inventory_sources.filter(last_job_failed=True) computed_fields = { 'has_active_failures': bool(failed_hosts.count()), @@ -417,6 +422,8 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): for field, value in computed_fields.items(): if getattr(iobj, field) != value: setattr(iobj, field, value) + # update in-memory object + setattr(self, field, value) else: computed_fields.pop(field) if computed_fields: @@ -464,6 +471,10 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): def save(self, *args, **kwargs): self._update_host_smart_inventory_memeberships() super(Inventory, self).save(*args, **kwargs) + if (self.kind == 'smart' and 'host_filter' in kwargs.get('update_fields', ['host_filter']) and + connection.vendor != 'sqlite'): + # Minimal update of host_count for smart inventory host filter changes + self.update_computed_fields(update_groups=False, update_hosts=False) def delete(self, *args, **kwargs): self._update_host_smart_inventory_memeberships() @@ -937,6 +948,8 @@ class InventorySourceOptions(BaseModel): ('satellite6', _('Red Hat Satellite 6')), ('cloudforms', _('Red Hat CloudForms')), ('openstack', _('OpenStack')), + ('rhv', _('Red Hat Virtualization')), + ('tower', _('Ansible Tower')), ('custom', _('Custom Script')), ] @@ -1185,6 +1198,16 @@ class InventorySourceOptions(BaseModel): """Red Hat CloudForms region choices (not implemented)""" return [('all', 'All')] + @classmethod + def get_rhv_region_choices(self): + """No region supprt""" + return [('all', 'All')] + + @classmethod + def get_tower_region_choices(self): + """No region supprt""" + return [('all', 'All')] + def clean_credential(self): if not self.source: return None @@ -1256,7 +1279,7 @@ class InventorySourceOptions(BaseModel): raise ValidationError(_('Invalid filter expression: %(filter)s') % {'filter': ', '.join(invalid_filters)}) return instance_filters - elif self.source == 'vmware': + elif self.source in ('vmware', 'tower'): return instance_filters else: return '' diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 5b1c327b12..488ea3d609 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -1,6 +1,6 @@ # Python import json -from copy import copy +from copy import copy, deepcopy # Django from django.db import models @@ -14,6 +14,7 @@ from awx.main.models.rbac import ( Role, RoleAncestorEntry, get_roles_on_resource ) from awx.main.utils import parse_yaml_or_json +from awx.main.utils.encryption import decrypt_value, get_encryption_key from awx.main.fields import JSONField, AskForField @@ -141,21 +142,27 @@ class SurveyJobTemplateMixin(models.Model): else: runtime_extra_vars = {} - # Overwrite with job template extra vars with survey default vars + # Overwrite job template extra vars with survey default vars if self.survey_enabled and 'spec' in self.survey_spec: for survey_element in self.survey_spec.get("spec", []): default = survey_element.get('default') variable_key = survey_element.get('variable') if survey_element.get('type') == 'password': - if variable_key in runtime_extra_vars and default: + if variable_key in runtime_extra_vars: kw_value = runtime_extra_vars[variable_key] - if kw_value.startswith('$encrypted$') and kw_value != default: - runtime_extra_vars[variable_key] = default + if kw_value == '$encrypted$': + runtime_extra_vars.pop(variable_key) if default is not None: - data = {variable_key: default} - errors = self._survey_element_validation(survey_element, data) + decrypted_default = default + if ( + survey_element['type'] == "password" and + isinstance(decrypted_default, basestring) and + decrypted_default.startswith('$encrypted$') + ): + decrypted_default = decrypt_value(get_encryption_key('value', pk=None), decrypted_default) + errors = self._survey_element_validation(survey_element, {variable_key: decrypted_default}) if not errors: survey_defaults[variable_key] = default extra_vars.update(survey_defaults) @@ -167,7 +174,20 @@ class SurveyJobTemplateMixin(models.Model): return create_kwargs def _survey_element_validation(self, survey_element, data): + # Don't apply validation to the `$encrypted$` placeholder; the decrypted + # default (if any) will be validated against instead errors = [] + + if (survey_element['type'] == "password"): + password_value = data.get(survey_element['variable']) + if ( + isinstance(password_value, basestring) and + password_value == '$encrypted$' + ): + if survey_element.get('default') is None and survey_element['required']: + errors.append("'%s' value missing" % survey_element['variable']) + return errors + if survey_element['variable'] not in data and survey_element['required']: errors.append("'%s' value missing" % survey_element['variable']) elif survey_element['type'] in ["textarea", "text", "password"]: @@ -272,6 +292,40 @@ class SurveyJobTemplateMixin(models.Model): return (accepted, rejected, errors) + @staticmethod + def pivot_spec(spec): + ''' + Utility method that will return a dictionary keyed off variable names + ''' + pivoted = {} + for element_data in spec.get('spec', []): + if 'variable' in element_data: + pivoted[element_data['variable']] = element_data + return pivoted + + def survey_variable_validation(self, data): + errors = [] + if not self.survey_enabled: + return errors + if 'name' not in self.survey_spec: + errors.append("'name' missing from survey spec.") + if 'description' not in self.survey_spec: + errors.append("'description' missing from survey spec.") + for survey_element in self.survey_spec.get("spec", []): + errors += self._survey_element_validation(survey_element, data) + return errors + + def display_survey_spec(self): + ''' + Hide encrypted default passwords in survey specs + ''' + survey_spec = deepcopy(self.survey_spec) if self.survey_spec else {} + for field in survey_spec.get('spec', []): + if field.get('type') == 'password': + if 'default' in field and field['default']: + field['default'] = '$encrypted$' + return survey_spec + class SurveyJobMixin(models.Model): class Meta: @@ -296,6 +350,20 @@ class SurveyJobMixin(models.Model): else: return self.extra_vars + def decrypted_extra_vars(self): + ''' + Decrypts fields marked as passwords in survey. + ''' + if self.survey_passwords: + extra_vars = json.loads(self.extra_vars) + for key in self.survey_passwords: + value = extra_vars.get(key) + if value and isinstance(value, basestring) and value.startswith('$encrypted$'): + extra_vars[key] = decrypt_value(get_encryption_key('value', pk=None), value) + return json.dumps(extra_vars) + else: + return self.extra_vars + class TaskManagerUnifiedJobMixin(models.Model): class Meta: @@ -312,6 +380,9 @@ class TaskManagerJobMixin(TaskManagerUnifiedJobMixin): class Meta: abstract = True + def get_jobs_fail_chain(self): + return [self.project_update] if self.project_update else [] + def dependent_jobs_finished(self): for j in self.dependent_jobs.all(): if j.status in ['pending', 'waiting', 'running']: diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 20e6923a1c..394cd556b2 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -91,6 +91,40 @@ class Schedule(CommonModel, LaunchTimeConfig): help_text=_("The next time that the scheduled action will run.") ) + # extra_data is actually a string with a JSON payload in it. This + # is technically OK because a string is a valid JSON. One day we will + # enforce non-string JSON. + def _clean_extra_data_system_jobs(self): + extra_data = self.extra_data + if not isinstance(extra_data, dict): + try: + extra_data = json.loads(self.extra_data) + except Exception: + raise ValidationError(_("Expected JSON")) + + if extra_data and 'days' in extra_data: + try: + if type(extra_data['days']) is bool: + raise ValueError + if float(extra_data['days']) != int(extra_data['days']): + raise ValueError + days = int(extra_data['days']) + if days < 0: + raise ValueError + except ValueError: + raise ValidationError(_("days must be a positive integer.")) + return self.extra_data + + def clean_extra_data(self): + if not self.unified_job_template: + return self.extra_data + + # Compare class by string name because it's hard to import SystemJobTemplate + if type(self.unified_job_template).__name__ is not 'SystemJobTemplate': + return self.extra_data + + return self._clean_extra_data_system_jobs() + def __unicode__(self): return u'%s_t%s_%s_%s' % (self.name, self.unified_job_template.id, self.id, self.next_run) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 235b048660..1d2a4eb221 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -34,7 +34,7 @@ from django_celery_results.models import TaskResult from awx.main.models.base import * # noqa from awx.main.models.mixins import ResourceMixin, TaskManagerUnifiedJobMixin from awx.main.utils import ( - decrypt_field, _inventory_updates, + encrypt_value, decrypt_field, _inventory_updates, copy_model_by_class, copy_m2m_relationships, get_type_for_model, parse_yaml_or_json ) @@ -345,6 +345,16 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio ''' new_job_passwords = kwargs.pop('survey_passwords', {}) eager_fields = kwargs.pop('_eager_fields', None) + + # automatically encrypt survey fields + if hasattr(self, 'survey_spec') and getattr(self, 'survey_enabled', False): + password_list = self.survey_password_variables() + for key in kwargs.get('extra_vars', {}): + if key in password_list: + kwargs['extra_vars'][key] = encrypt_value( + kwargs['extra_vars'][key] + ) + unified_job_class = self._get_unified_job_class() fields = self._get_unified_job_field_names() unallowed_fields = set(kwargs.keys()) - set(fields) diff --git a/awx/main/signals.py b/awx/main/signals.py index efda19f0cd..f3e1138a10 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -26,6 +26,8 @@ from awx.main.fields import is_implicit_parent from awx.main.consumers import emit_channel_notification +from awx.conf.utils import conf_to_dict + __all__ = [] logger = logging.getLogger('awx.main.signals') @@ -284,7 +286,12 @@ def _update_host_last_jhs(host): except IndexError: jhs = None update_fields = [] - last_job = jhs.job if jhs else None + try: + last_job = jhs.job if jhs else None + except Job.DoesNotExist: + # The job (and its summaries) have already been/are currently being + # deleted, so there's no need to update the host w/ a reference to it + return if host.last_job != last_job: host.last_job = last_job update_fields.append('last_job') @@ -392,12 +399,15 @@ def activity_stream_create(sender, instance, created, **kwargs): object1=object1, changes=json.dumps(changes), actor=get_current_user_or_none()) - activity_entry.save() #TODO: Weird situation where cascade SETNULL doesn't work # it might actually be a good idea to remove all of these FK references since # we don't really use them anyway. if instance._meta.model_name != 'setting': # Is not conf.Setting instance + activity_entry.save() getattr(activity_entry, object1).add(instance) + else: + activity_entry.setting = conf_to_dict(instance) + activity_entry.save() def activity_stream_update(sender, instance, **kwargs): @@ -423,9 +433,12 @@ def activity_stream_update(sender, instance, **kwargs): object1=object1, changes=json.dumps(changes), actor=get_current_user_or_none()) - activity_entry.save() if instance._meta.model_name != 'setting': # Is not conf.Setting instance + activity_entry.save() getattr(activity_entry, object1).add(instance) + else: + activity_entry.setting = conf_to_dict(instance) + activity_entry.save() def activity_stream_delete(sender, instance, **kwargs): @@ -535,8 +548,8 @@ def get_current_user_from_drf_request(sender, **kwargs): drf_request on the underlying Django Request object. ''' request = get_current_request() - drf_request = getattr(request, 'drf_request', None) - return (getattr(drf_request, 'user', False), 0) + drf_request_user = getattr(request, 'drf_request_user', False) + return (drf_request_user, 0) @receiver(pre_delete, sender=Organization) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index ac33b497cc..f1e4721bb3 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -432,13 +432,22 @@ def update_host_smart_inventory_memberships(): smart_inventories = Inventory.objects.filter(kind='smart', host_filter__isnull=False, pending_deletion=False) SmartInventoryMembership.objects.all().delete() memberships = [] + changed_inventories = set([]) for smart_inventory in smart_inventories: - memberships.extend([SmartInventoryMembership(inventory_id=smart_inventory.id, host_id=host_id[0]) - for host_id in smart_inventory.hosts.values_list('id')]) + add_for_inventory = [ + SmartInventoryMembership(inventory_id=smart_inventory.id, host_id=host_id[0]) + for host_id in smart_inventory.hosts.values_list('id') + ] + memberships.extend(add_for_inventory) + if add_for_inventory: + changed_inventories.add(smart_inventory) SmartInventoryMembership.objects.bulk_create(memberships) except IntegrityError as e: logger.error("Update Host Smart Inventory Memberships failed due to an exception: " + str(e)) return + # Update computed fields for changed inventories outside atomic action + for smart_inventory in changed_inventories: + smart_inventory.update_computed_fields(update_groups=False, update_hosts=False) @shared_task(bind=True, queue='tower', base=LogErrorsTask, max_retries=5) @@ -874,6 +883,12 @@ class BaseTask(LogErrorsTask): try: stdout_handle.flush() stdout_handle.close() + # If stdout_handle was wrapped with event filter, log data + if hasattr(stdout_handle, '_event_ct'): + logger.info('%s finished running, producing %s events.', + instance.log_format, stdout_handle._event_ct) + else: + logger.info('%s finished running', instance.log_format) except Exception: pass @@ -1026,13 +1041,9 @@ class RunJob(BaseTask): env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' env['TOWER_HOST'] = settings.TOWER_URL_BASE env['AWX_HOST'] = settings.TOWER_URL_BASE - env['CALLBACK_QUEUE'] = settings.CALLBACK_QUEUE - env['CALLBACK_CONNECTION'] = settings.CELERY_BROKER_URL + env['REST_API_URL'] = settings.INTERNAL_API_URL + env['REST_API_TOKEN'] = job.task_auth_token or '' env['CACHE'] = settings.CACHES['default']['LOCATION'] if 'LOCATION' in settings.CACHES['default'] else '' - if getattr(settings, 'JOB_CALLBACK_DEBUG', False): - env['JOB_CALLBACK_DEBUG'] = '2' - elif settings.DEBUG: - env['JOB_CALLBACK_DEBUG'] = '1' # Create a directory for ControlPath sockets that is unique to each # job and visible inside the proot environment (when enabled). @@ -1067,6 +1078,8 @@ class RunJob(BaseTask): env['AZURE_SUBSCRIPTION_ID'] = cloud_cred.subscription env['AZURE_AD_USER'] = cloud_cred.username env['AZURE_PASSWORD'] = decrypt_field(cloud_cred, 'password') + if cloud_cred.inputs.get('cloud_environment', None): + env['AZURE_CLOUD_ENVIRONMENT'] = cloud_cred.inputs['cloud_environment'] elif cloud_cred and cloud_cred.kind == 'vmware': env['VMWARE_USER'] = cloud_cred.username env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password') @@ -1159,7 +1172,7 @@ class RunJob(BaseTask): if kwargs.get('display', False) and job.job_template: extra_vars.update(json.loads(job.display_extra_vars())) else: - extra_vars.update(job.extra_vars_dict) + extra_vars.update(json.loads(job.decrypted_extra_vars())) args.extend(['-e', json.dumps(extra_vars)]) # Add path to playbook (relative to project.local_path). @@ -1252,10 +1265,12 @@ class RunJob(BaseTask): task_instance.run(local_project_sync.id) job = self.update_model(job.pk, scm_revision=job.project.scm_revision) except Exception: - job = self.update_model(job.pk, status='failed', - job_explanation=('Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % - ('project_update', local_project_sync.name, local_project_sync.id))) - raise + local_project_sync.refresh_from_db() + if local_project_sync.status != 'canceled': + job = self.update_model(job.pk, status='failed', + job_explanation=('Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % + ('project_update', local_project_sync.name, local_project_sync.id))) + raise if job.use_fact_cache and not kwargs.get('isolated'): job.start_job_fact_cache() @@ -1327,6 +1342,9 @@ class RunProjectUpdate(BaseTask): env['ANSIBLE_ASK_PASS'] = str(False) env['ANSIBLE_BECOME_ASK_PASS'] = str(False) env['DISPLAY'] = '' # Prevent stupid password popup when running tests. + # give ansible a hint about the intended tmpdir to work around issues + # like https://github.com/ansible/ansible/issues/30064 + env['TMP'] = settings.AWX_PROOT_BASE_PATH return env def _build_scm_url_extra_vars(self, project_update, **kwargs): @@ -1519,11 +1537,11 @@ class RunProjectUpdate(BaseTask): except InventoryUpdate.DoesNotExist: logger.warning('%s Dependent inventory update deleted during execution.', project_update.log_format) continue - if project_update.cancel_flag or local_inv_update.cancel_flag: - if not project_update.cancel_flag: - self.update_model(project_update.pk, cancel_flag=True, job_explanation=_( - 'Dependent inventory update {} was canceled.'.format(local_inv_update.name))) - break # Stop rest of updates if project or inventory update was canceled + if project_update.cancel_flag: + logger.info('Project update {} was canceled while updating dependent inventories.'.format(project_update.log_format)) + break + if local_inv_update.cancel_flag: + logger.info('Continuing to process project dependencies after {} was canceled'.format(local_inv_update.log_format)) if local_inv_update.status == 'successful': inv_src.scm_last_revision = scm_revision inv_src.save(update_fields=['scm_last_revision']) @@ -1864,11 +1882,24 @@ class RunInventoryUpdate(BaseTask): env['AZURE_AD_USER'] = passwords.get('source_username', '') env['AZURE_PASSWORD'] = passwords.get('source_password', '') env['AZURE_INI_PATH'] = cloud_credential + if inventory_update.credential and \ + inventory_update.credential.inputs.get('cloud_environment', None): + env['AZURE_CLOUD_ENVIRONMENT'] = inventory_update.credential.inputs['cloud_environment'] elif inventory_update.source == 'gce': env['GCE_EMAIL'] = passwords.get('source_username', '') env['GCE_PROJECT'] = passwords.get('source_project', '') env['GCE_PEM_FILE_PATH'] = cloud_credential env['GCE_ZONE'] = inventory_update.source_regions if inventory_update.source_regions != 'all' else '' + + # by default, the GCE inventory source caches results on disk for + # 5 minutes; disable this behavior + cp = ConfigParser.ConfigParser() + cp.add_section('cache') + cp.set('cache', 'cache_max_age', '0') + handle, path = tempfile.mkstemp(dir=kwargs.get('private_data_dir', None)) + cp.write(os.fdopen(handle, 'w')) + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) + env['GCE_INI_PATH'] = path elif inventory_update.source == 'openstack': env['OS_CLIENT_CONFIG_FILE'] = cloud_credential elif inventory_update.source == 'satellite6': @@ -1879,6 +1910,9 @@ class RunInventoryUpdate(BaseTask): for env_k in inventory_update.source_vars_dict: if str(env_k) not in env and str(env_k) not in settings.INV_ENV_VARIABLE_BLACKLIST: env[str(env_k)] = unicode(inventory_update.source_vars_dict[env_k]) + elif inventory_update.source == 'tower': + env['TOWER_INVENTORY'] = inventory_update.instance_filters + env['TOWER_LICENSE_TYPE'] = get_licenser().validate()['license_type'] elif inventory_update.source == 'file': raise NotImplementedError('Cannot update file sources through the task system.') # add private_data_files @@ -2066,14 +2100,10 @@ class RunAdHocCommand(BaseTask): env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir env['ANSIBLE_LOAD_CALLBACK_PLUGINS'] = '1' env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' # Hardcoded by Ansible for ad-hoc commands (either minimal or oneline). - env['CALLBACK_QUEUE'] = settings.CALLBACK_QUEUE - env['CALLBACK_CONNECTION'] = settings.CELERY_BROKER_URL + env['REST_API_URL'] = settings.INTERNAL_API_URL + env['REST_API_TOKEN'] = ad_hoc_command.task_auth_token or '' env['ANSIBLE_SFTP_BATCH_MODE'] = 'False' env['CACHE'] = settings.CACHES['default']['LOCATION'] if 'LOCATION' in settings.CACHES['default'] else '' - if getattr(settings, 'JOB_CALLBACK_DEBUG', False): - env['JOB_CALLBACK_DEBUG'] = '2' - elif settings.DEBUG: - env['JOB_CALLBACK_DEBUG'] = '1' # Specify empty SSH args (should disable ControlPersist entirely for # ad hoc commands). @@ -2124,14 +2154,27 @@ class RunAdHocCommand(BaseTask): if ad_hoc_command.verbosity: args.append('-%s' % ('v' * min(5, ad_hoc_command.verbosity))) + # Define special extra_vars for AWX, combine with ad_hoc_command.extra_vars + extra_vars = { + 'tower_job_id': ad_hoc_command.pk, + 'awx_job_id': ad_hoc_command.pk, + } + if ad_hoc_command.created_by: + extra_vars.update({ + 'tower_user_id': ad_hoc_command.created_by.pk, + 'tower_user_name': ad_hoc_command.created_by.username, + 'awx_user_id': ad_hoc_command.created_by.pk, + 'awx_user_name': ad_hoc_command.created_by.username, + }) + if ad_hoc_command.extra_vars_dict: redacted_extra_vars, removed_vars = extract_ansible_vars(ad_hoc_command.extra_vars_dict) if removed_vars: raise ValueError(_( "{} are prohibited from use in ad hoc commands." ).format(", ".join(removed_vars))) - - args.extend(['-e', json.dumps(ad_hoc_command.extra_vars_dict)]) + extra_vars.update(ad_hoc_command.extra_vars_dict) + args.extend(['-e', json.dumps(extra_vars)]) args.extend(['-m', ad_hoc_command.module_name]) args.extend(['-a', ad_hoc_command.module_args]) diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index 1396ac3b48..382add5dc6 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -5,6 +5,7 @@ from awx.api.versioning import reverse from awx.main.middleware import ActivityStreamMiddleware from awx.main.models.activity_stream import ActivityStream from awx.main.access import ActivityStreamAccess +from awx.conf.models import Setting def mock_feature_enabled(feature): @@ -47,6 +48,26 @@ def test_basic_fields(monkeypatch, organization, get, user, settings): assert response.data['summary_fields']['organization'][0]['name'] == 'test-org' +@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) +@pytest.mark.django_db +def test_ctint_activity_stream(monkeypatch, get, user, settings): + Setting.objects.create(key="FOO", value="bar") + settings.ACTIVITY_STREAM_ENABLED = True + u = user('admin', True) + activity_stream = ActivityStream.objects.filter(setting={'name': 'FOO', 'category': None}).latest('pk') + activity_stream.actor = u + activity_stream.save() + + aspk = activity_stream.pk + url = reverse('api:activity_stream_detail', kwargs={'pk': aspk}) + response = get(url, user('admin', True)) + + assert response.status_code == 200 + assert 'summary_fields' in response.data + assert 'setting' in response.data['summary_fields'] + assert response.data['summary_fields']['setting'][0]['name'] == 'FOO' + + @mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_middleware_actor_added(monkeypatch, post, get, user, settings): diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py index feebfc08f4..e26447deea 100644 --- a/awx/main/tests/functional/api/test_credential.py +++ b/awx/main/tests/functional/api/test_credential.py @@ -4,7 +4,9 @@ import re import mock # noqa import pytest -from awx.main.models.credential import Credential, CredentialType +from awx.main.models import (AdHocCommand, Credential, CredentialType, Job, JobTemplate, + Inventory, InventorySource, Project, + WorkflowJobNode) from awx.main.utils import decrypt_field from awx.api.versioning import reverse @@ -12,6 +14,17 @@ EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY- EXAMPLE_ENCRYPTED_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nxyz==\n-----END PRIVATE KEY-----' +@pytest.mark.django_db +def test_idempotent_credential_type_setup(): + assert CredentialType.objects.count() == 0 + CredentialType.setup_tower_managed_defaults() + total = CredentialType.objects.count() + assert total > 0 + + CredentialType.setup_tower_managed_defaults() + assert CredentialType.objects.count() == total + + @pytest.mark.django_db @pytest.mark.parametrize('kind, total', [ ('ssh', 1), ('net', 0) @@ -575,7 +588,7 @@ def test_create_org_credential_as_admin(post, organization, org_admin, credentia params['name'] = 'Some name' params['organization'] = organization.id response = post( - reverse('api:credential_list'), + reverse('api:credential_list', kwargs={'version': version}), params, org_admin ) @@ -591,7 +604,7 @@ def test_credential_detail(post, get, organization, org_admin, credentialtype_ss params['name'] = 'Some name' params['organization'] = organization.id response = post( - reverse('api:credential_list'), + reverse('api:credential_list', kwargs={'version': version}), params, org_admin ) @@ -1410,7 +1423,17 @@ def test_field_removal(put, organization, admin, credentialtype_ssh, version, pa @pytest.mark.django_db -def test_credential_type_immutable_in_v2(patch, organization, admin, credentialtype_ssh, credentialtype_aws): +@pytest.mark.parametrize('relation, related_obj', [ + ['ad_hoc_commands', AdHocCommand()], + ['insights_inventories', Inventory()], + ['inventorysources', InventorySource()], + ['jobs', Job()], + ['jobtemplates', JobTemplate()], + ['projects', Project()], + ['workflowjobnodes', WorkflowJobNode()], +]) +def test_credential_type_mutability(patch, organization, admin, credentialtype_ssh, + credentialtype_aws, relation, related_obj): cred = Credential( credential_type=credentialtype_ssh, name='Best credential ever', @@ -1422,19 +1445,39 @@ def test_credential_type_immutable_in_v2(patch, organization, admin, credentialt ) cred.save() + related_obj.save() + getattr(cred, relation).add(related_obj) + + def _change_credential_type(): + return patch( + reverse('api:credential_detail', kwargs={'version': 'v2', 'pk': cred.pk}), + { + 'credential_type': credentialtype_aws.pk, + 'inputs': { + 'username': u'jim', + 'password': u'pass' + } + }, + admin + ) + + response = _change_credential_type() + assert response.status_code == 400 + expected = ['You cannot change the credential type of the credential, ' + 'as it may break the functionality of the resources using it.'] + assert response.data['credential_type'] == expected + response = patch( reverse('api:credential_detail', kwargs={'version': 'v2', 'pk': cred.pk}), - { - 'credential_type': credentialtype_aws.pk, - 'inputs': { - 'username': u'jim', - 'password': u'pass' - } - }, + {'name': 'Worst credential ever'}, admin ) - assert response.status_code == 400 - assert 'credential_type' in response.data + assert response.status_code == 200 + assert Credential.objects.get(pk=cred.pk).name == 'Worst credential ever' + + related_obj.delete() + response = _change_credential_type() + assert response.status_code == 200 @pytest.mark.django_db diff --git a/awx/main/tests/functional/api/test_generic.py b/awx/main/tests/functional/api/test_generic.py index 6b2d580d2d..4d68b43ead 100644 --- a/awx/main/tests/functional/api/test_generic.py +++ b/awx/main/tests/functional/api/test_generic.py @@ -60,3 +60,34 @@ def test_proxy_ip_whitelist(get, patch, admin): REMOTE_HOST='my.proxy.example.org', HTTP_X_FROM_THE_LOAD_BALANCER='some-actual-ip') assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip' + + +@pytest.mark.django_db +class TestDeleteViews: + def test_sublist_delete_permission_check(self, inventory_source, host, rando, delete): + inventory_source.hosts.add(host) + inventory_source.inventory.read_role.members.add(rando) + delete( + reverse( + 'api:inventory_source_hosts_list', + kwargs={'version': 'v2', 'pk': inventory_source.pk} + ), user=rando, expect=403 + ) + + def test_sublist_delete_functionality(self, inventory_source, host, rando, delete): + inventory_source.hosts.add(host) + inventory_source.inventory.admin_role.members.add(rando) + delete( + reverse( + 'api:inventory_source_hosts_list', + kwargs={'version': 'v2', 'pk': inventory_source.pk} + ), user=rando, expect=204 + ) + assert inventory_source.hosts.count() == 0 + + def test_destroy_permission_check(self, job_factory, system_auditor, delete): + job = job_factory() + resp = delete( + job.get_absolute_url(), user=system_auditor + ) + assert resp.status_code == 403 diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index c1b84401a5..c96bb8057c 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import pytest import mock @@ -236,6 +237,51 @@ def test_create_inventory_smart_inventory_sources(post, get, inventory, admin_us assert jdata['count'] == 0 +@pytest.mark.django_db +def test_urlencode_host_filter(post, admin_user, organization): + """ + Host filters saved on the model must correspond to the same result + as when that host_filter is used in the URL as a querystring. + That means that it must be url-encoded patterns like %22 for quotes + must be escaped as the string is saved to the model. + + Expected host filter in this test would match a host such as: + inventory.hosts.create( + ansible_facts={"ansible_distribution_version": "7.4"} + ) + """ + # Create smart inventory with host filter that corresponds to querystring + post( + reverse('api:inventory_list'), + data={ + 'name': 'smart inventory', 'kind': 'smart', + 'organization': organization.pk, + 'host_filter': 'ansible_facts__ansible_distribution_version=%227.4%22' + }, + user=admin_user, + expect=201 + ) + # Assert that the saved version of host filter has escaped "" + si = Inventory.objects.get(name='smart inventory') + assert si.host_filter == 'ansible_facts__ansible_distribution_version="7.4"' + + +@pytest.mark.django_db +def test_host_filter_unicode(post, admin_user, organization): + post( + reverse('api:inventory_list'), + data={ + 'name': 'smart inventory', 'kind': 'smart', + 'organization': organization.pk, + 'host_filter': u'ansible_facts__ansible_distribution=レッドハット' + }, + user=admin_user, + expect=201 + ) + si = Inventory.objects.get(name='smart inventory') + assert si.host_filter == u'ansible_facts__ansible_distribution=レッドハット' + + @pytest.mark.parametrize("role_field,expected_status_code", [ (None, 403), ('admin_role', 201), diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index f1198545ca..5974a1a935 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -304,3 +304,19 @@ def test_isolated_keys_readonly(get, patch, delete, admin, key, expected): delete(url, user=admin) assert getattr(settings, key) == 'secret' + + +@pytest.mark.django_db +def test_isolated_key_flag_readonly(get, patch, delete, admin): + settings.AWX_ISOLATED_KEY_GENERATION = True + url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'jobs'}) + resp = get(url, user=admin) + assert resp.data['AWX_ISOLATED_KEY_GENERATION'] is True + + patch(url, user=admin, data={ + 'AWX_ISOLATED_KEY_GENERATION': False + }) + assert settings.AWX_ISOLATED_KEY_GENERATION is True + + delete(url, user=admin) + assert settings.AWX_ISOLATED_KEY_GENERATION is True diff --git a/awx/main/tests/functional/api/test_survey_spec.py b/awx/main/tests/functional/api/test_survey_spec.py index cdb90f0af2..20155ac654 100644 --- a/awx/main/tests/functional/api/test_survey_spec.py +++ b/awx/main/tests/functional/api/test_survey_spec.py @@ -111,6 +111,241 @@ def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post, assert updated_jt.survey_spec == survey_input_data +@mock.patch('awx.api.views.feature_enabled', lambda feature: True) +@pytest.mark.django_db +@pytest.mark.parametrize('with_default', [True, False]) +@pytest.mark.parametrize('value, status', [ + ('SUPERSECRET', 201), + (['some', 'invalid', 'list'], 400), + ({'some-invalid': 'dict'}, 400), + (False, 400) +]) +def test_survey_spec_passwords_are_encrypted_on_launch(job_template_factory, post, admin_user, with_default, value, status): + objects = job_template_factory('jt', organization='org1', project='prj', + inventory='inv', credential='cred') + job_template = objects.job_template + job_template.survey_enabled = True + job_template.save() + input_data = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': True, + 'variable': 'secret_value', + 'type': 'password' + }], + 'name': 'my survey' + } + if with_default: + input_data['spec'][0]['default'] = 'some-default' + post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=200) + resp = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), + dict(extra_vars=dict(secret_value=value)), admin_user, expect=status) + + if status == 201: + job = Job.objects.get(pk=resp.data['id']) + assert json.loads(job.extra_vars)['secret_value'].startswith('$encrypted$') + assert json.loads(job.decrypted_extra_vars()) == { + 'secret_value': value + } + else: + assert "for 'secret_value' expected to be a string." in json.dumps(resp.data) + + +@mock.patch('awx.api.views.feature_enabled', lambda feature: True) +@pytest.mark.django_db +def test_survey_spec_passwords_with_empty_default(job_template_factory, post, admin_user): + objects = job_template_factory('jt', organization='org1', project='prj', + inventory='inv', credential='cred') + job_template = objects.job_template + job_template.survey_enabled = True + job_template.save() + input_data = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': False, + 'variable': 'secret_value', + 'type': 'password', + 'default': '' + }], + 'name': 'my survey' + } + post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=200) + + resp = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), + {}, admin_user, expect=201) + job = Job.objects.get(pk=resp.data['id']) + assert json.loads(job.extra_vars)['secret_value'] == '' + assert json.loads(job.decrypted_extra_vars()) == { + 'secret_value': '' + } + + +@mock.patch('awx.api.views.feature_enabled', lambda feature: True) +@pytest.mark.django_db +@pytest.mark.parametrize('default, launch_value, expected_extra_vars, status', [ + ['', '$encrypted$', {'secret_value': ''}, 201], + ['', 'y', {'secret_value': 'y'}, 201], + ['', 'y' * 100, None, 400], + [None, '$encrypted$', {}, 201], + [None, 'y', {'secret_value': 'y'}, 201], + [None, 'y' * 100, {}, 400], + ['x', '$encrypted$', {'secret_value': 'x'}, 201], + ['x', 'y', {'secret_value': 'y'}, 201], + ['x', 'y' * 100, {}, 400], + ['x' * 100, '$encrypted$', {}, 201], + ['x' * 100, 'y', {'secret_value': 'y'}, 201], + ['x' * 100, 'y' * 100, {}, 400], +]) +def test_survey_spec_passwords_with_default_optional(job_template_factory, post, admin_user, + default, launch_value, + expected_extra_vars, status): + objects = job_template_factory('jt', organization='org1', project='prj', + inventory='inv', credential='cred') + job_template = objects.job_template + job_template.survey_enabled = True + job_template.save() + input_data = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': False, + 'variable': 'secret_value', + 'type': 'password', + 'max': 3 + }], + 'name': 'my survey' + } + if default is not None: + input_data['spec'][0]['default'] = default + post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=200) + + resp = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), + data={'extra_vars': {'secret_value': launch_value}}, user=admin_user, expect=status) + + if status == 201: + job = Job.objects.get(pk=resp.data['job']) + assert json.loads(job.decrypted_extra_vars()) == expected_extra_vars + if default: + assert default not in json.loads(job.extra_vars).values() + assert launch_value not in json.loads(job.extra_vars).values() + + +@mock.patch('awx.api.views.feature_enabled', lambda feature: True) +@pytest.mark.django_db +@pytest.mark.parametrize('default, launch_value, expected_extra_vars, status', [ + ['', '$encrypted$', {'secret_value': ''}, 201], + [None, '$encrypted$', {}, 400], + [None, 'y', {'secret_value': 'y'}, 201], +]) +def test_survey_spec_passwords_with_default_required(job_template_factory, post, admin_user, + default, launch_value, + expected_extra_vars, status): + objects = job_template_factory('jt', organization='org1', project='prj', + inventory='inv', credential='cred') + job_template = objects.job_template + job_template.survey_enabled = True + job_template.save() + input_data = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': True, + 'variable': 'secret_value', + 'type': 'password', + 'max': 3 + }], + 'name': 'my survey' + } + if default is not None: + input_data['spec'][0]['default'] = default + post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=200) + + resp = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), + data={'extra_vars': {'secret_value': launch_value}}, user=admin_user, expect=status) + + if status == 201: + job = Job.objects.get(pk=resp.data['job']) + assert json.loads(job.decrypted_extra_vars()) == expected_extra_vars + if default: + assert default not in json.loads(job.extra_vars).values() + assert launch_value not in json.loads(job.extra_vars).values() + + +@mock.patch('awx.api.views.feature_enabled', lambda feature: True) +@pytest.mark.django_db +@pytest.mark.parametrize('default, status', [ + ('SUPERSECRET', 200), + (['some', 'invalid', 'list'], 400), + ({'some-invalid': 'dict'}, 400), + (False, 400) +]) +def test_survey_spec_default_passwords_are_encrypted(job_template, post, admin_user, default, status): + job_template.survey_enabled = True + job_template.save() + input_data = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': True, + 'variable': 'secret_value', + 'default': default, + 'type': 'password' + }], + 'name': 'my survey' + } + resp = post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=status) + + if status == 200: + updated_jt = JobTemplate.objects.get(pk=job_template.pk) + assert updated_jt.survey_spec['spec'][0]['default'].startswith('$encrypted$') + + job = updated_jt.create_unified_job() + assert json.loads(job.extra_vars)['secret_value'].startswith('$encrypted$') + assert json.loads(job.decrypted_extra_vars()) == { + 'secret_value': default + } + else: + assert "for 'secret_value' expected to be a string." in str(resp.data) + + +@mock.patch('awx.api.views.feature_enabled', lambda feature: True) +@pytest.mark.django_db +def test_survey_spec_default_passwords_encrypted_on_update(job_template, post, put, admin_user): + input_data = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': True, + 'variable': 'secret_value', + 'default': 'SUPERSECRET', + 'type': 'password' + }], + 'name': 'my survey' + } + post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=200) + updated_jt = JobTemplate.objects.get(pk=job_template.pk) + + # simulate a survey field edit where we're not changing the default value + input_data['spec'][0]['default'] = '$encrypted$' + post(url=reverse('api:job_template_survey_spec', kwargs={'pk': job_template.id}), + data=input_data, user=admin_user, expect=200) + assert updated_jt.survey_spec == JobTemplate.objects.get(pk=job_template.pk).survey_spec + + # Tests related to survey content validation @mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db diff --git a/awx/main/tests/functional/test_credential.py b/awx/main/tests/functional/test_credential.py index 5bae4cc065..1fe909c092 100644 --- a/awx/main/tests/functional/test_credential.py +++ b/awx/main/tests/functional/test_credential.py @@ -25,9 +25,11 @@ def test_default_cred_types(): 'insights', 'net', 'openstack', + 'rhv', 'satellite6', 'scm', 'ssh', + 'tower', 'vault', 'vmware', ] diff --git a/awx/main/tests/functional/test_rbac_inventory.py b/awx/main/tests/functional/test_rbac_inventory.py index 821d8893a8..830e5a7b52 100644 --- a/awx/main/tests/functional/test_rbac_inventory.py +++ b/awx/main/tests/functional/test_rbac_inventory.py @@ -13,7 +13,7 @@ from awx.main.access import ( InventoryUpdateAccess, CustomInventoryScriptAccess, ScheduleAccess, - StateConflict + ActiveJobConflict ) @@ -21,7 +21,7 @@ from awx.main.access import ( def test_running_job_protection(inventory, admin_user): AdHocCommand.objects.create(inventory=inventory, status='running') access = InventoryAccess(admin_user) - with pytest.raises(StateConflict): + with pytest.raises(ActiveJobConflict): access.can_delete(inventory) diff --git a/awx/main/tests/functional/test_rbac_workflow.py b/awx/main/tests/functional/test_rbac_workflow.py index 64cd0b6bbb..0ae3b3c0a0 100644 --- a/awx/main/tests/functional/test_rbac_workflow.py +++ b/awx/main/tests/functional/test_rbac_workflow.py @@ -57,7 +57,21 @@ class TestWorkflowJobTemplateNodeAccess: # without access to the related job template, admin to the WFJT can # not change the prompted parameters access = WorkflowJobTemplateNodeAccess(org_admin) - assert not access.can_change(wfjt_node, {'job_type': 'scan'}) + assert not access.can_change(wfjt_node, {'job_type': 'check'}) + + def test_node_edit_allowed(self, wfjt_node, org_admin): + wfjt_node.unified_job_template.admin_role.members.add(org_admin) + access = WorkflowJobTemplateNodeAccess(org_admin) + assert access.can_change(wfjt_node, {'job_type': 'check'}) + + def test_prompted_resource_prevents_edit(self, wfjt_node, org_admin, machine_credential): + # without access to prompted resources, admin to the WFJT can + # not change the other prompted resources + wfjt_node.unified_job_template.admin_role.members.add(org_admin) + wfjt_node.credential = machine_credential + wfjt_node.save() + access = WorkflowJobTemplateNodeAccess(org_admin) + assert not access.can_change(wfjt_node, {'inventory': 45}) def test_access_to_edit_non_JT(self, rando, workflow_job_template, organization, project): workflow_job_template.admin_role.members.add(rando) diff --git a/awx/main/tests/functional/test_reencrypt_migration.py b/awx/main/tests/functional/test_reencrypt_migration.py index 3201866893..d0ec9ab054 100644 --- a/awx/main/tests/functional/test_reencrypt_migration.py +++ b/awx/main/tests/functional/test_reencrypt_migration.py @@ -10,6 +10,10 @@ from django.apps import apps from awx.main.models import ( UnifiedJob, + Job, + JobTemplate, + WorkflowJob, + WorkflowJobTemplate, NotificationTemplate, Credential, ) @@ -20,9 +24,10 @@ from awx.main.migrations._reencrypt import ( _notification_templates, _credentials, _unified_jobs, + _encrypt_survey_passwords ) -from awx.main.utils import decrypt_field +from awx.main.utils import decrypt_field, get_encryption_key, decrypt_value @pytest.mark.django_db @@ -93,3 +98,62 @@ def test_unified_job_migration(old_enc, new_enc, value): # Exception if the encryption type of AESCBC is not properly skipped, ensures # our `startswith` calls don't have typos _unified_jobs(apps) + + +@pytest.mark.django_db +@pytest.mark.parametrize("attr, cls", [ + ['job_template', JobTemplate], + ['workflow_job_template', WorkflowJobTemplate] +]) +def test_survey_default_password_encryption(attr, cls, request): + factory = request.getfuncargvalue('{}_factory'.format(attr)) + jt = getattr(factory('jt'), attr) + jt.survey_enabled = True + jt.survey_spec = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': True, + 'variable': 'secret_value', + 'default': 'SUPERSECRET', + 'type': 'password' + }], + 'name': 'my survey' + } + jt.save() + + _encrypt_survey_passwords(Job, JobTemplate, WorkflowJob, WorkflowJobTemplate) + spec = cls.objects.get(pk=jt.pk).survey_spec['spec'] + assert decrypt_value(get_encryption_key('value', pk=None), spec[0]['default']) == 'SUPERSECRET' + + +@pytest.mark.django_db +@pytest.mark.parametrize("attr, cls", [ + ['job_template', Job], + ['workflow_job_template', WorkflowJob] +]) +def test_job_survey_vars_encryption(attr, cls, request): + factory = request.getfuncargvalue('{}_factory'.format(attr)) + jt = getattr(factory('jt'), attr) + jt.survey_enabled = True + jt.survey_spec = { + 'description': 'A survey', + 'spec': [{ + 'index': 0, + 'question_name': 'What is your password?', + 'required': True, + 'variable': 'secret_value', + 'default': '', + 'type': 'password' + }], + 'name': 'my survey' + } + jt.save() + job = jt.create_unified_job() + job.extra_vars = json.dumps({'secret_value': 'SUPERSECRET'}) + job.save() + + _encrypt_survey_passwords(Job, JobTemplate, WorkflowJob, WorkflowJobTemplate) + job = cls.objects.get(pk=job.pk) + assert json.loads(job.decrypted_extra_vars()) == {'secret_value': 'SUPERSECRET'} diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py index 8014e9c4a6..1f510b2054 100644 --- a/awx/main/tests/unit/api/test_views.py +++ b/awx/main/tests/unit/api/test_views.py @@ -1,15 +1,16 @@ import mock import pytest import requests +from copy import deepcopy from collections import namedtuple from awx.api.views import ( ApiVersionRootView, JobTemplateLabelList, - JobTemplateSurveySpec, InventoryInventorySourcesUpdate, HostInsights, + JobTemplateSurveySpec ) from awx.main.models import ( @@ -77,19 +78,6 @@ class TestJobTemplateLabelList: assert mixin_unattach.called_with(mock_request, None, None) -class TestJobTemplateSurveySpec(object): - @mock.patch('awx.api.views.feature_enabled', lambda feature: True) - def test_get_password_type(self, mocker, mock_response_new): - JobTemplate = namedtuple('JobTemplate', 'survey_spec') - obj = JobTemplate(survey_spec={'spec':[{'type': 'password', 'default': 'my_default'}]}) - with mocker.patch.object(JobTemplateSurveySpec, 'get_object', return_value=obj): - view = JobTemplateSurveySpec() - response = view.get(mocker.MagicMock()) - assert response == mock_response_new - # which there was a better way to do this! - assert response.call_args[0][1]['spec'][0]['default'] == '$encrypted$' - - class TestInventoryInventorySourcesUpdate: @pytest.mark.parametrize("can_update, can_access, is_source, is_up_on_proj, expected", [ @@ -220,3 +208,158 @@ class TestHostInsights(): assert resp.data['error'] == 'The Insights Credential for "inventory_name_here" was not found.' assert resp.status_code == 404 + + +class TestInventoryHostsList(object): + + def test_host_list_smart_inventory(self, mocker): + Inventory = namedtuple('Inventory', ['kind', 'host_filter', 'hosts', 'organization_id']) + obj = Inventory(kind='smart', host_filter='localhost', hosts=HostManager(), organization_id=None) + obj.hosts.instance = obj + + with mock.patch.object(InventoryHostsList, 'get_parent_object', return_value=obj): + with mock.patch('awx.main.utils.filters.SmartFilter.query_from_string') as mock_query: + view = InventoryHostsList() + view.get_queryset() + mock_query.assert_called_once_with('localhost') + + +class TestSurveySpecValidation: + + def test_create_text_encrypted(self): + view = JobTemplateSurveySpec() + resp = view._validate_spec_data({ + "name": "new survey", + "description": "foobar", + "spec": [ + { + "question_description": "", + "min": 0, + "default": "$encrypted$", + "max": 1024, + "required": True, + "choices": "", + "variable": "openshift_username", + "question_name": "OpenShift Username", + "type": "text" + } + ] + }, {}) + assert resp.status_code == 400 + assert '$encrypted$ is a reserved keyword for password question defaults' in str(resp.data['error']) + + def test_change_encrypted_var_name(self): + view = JobTemplateSurveySpec() + old = { + "name": "old survey", + "description": "foobar", + "spec": [ + { + "question_description": "", + "min": 0, + "default": "$encrypted$foooooooo", + "max": 1024, + "required": True, + "choices": "", + "variable": "openshift_username", + "question_name": "OpenShift Username", + "type": "password" + } + ] + } + new = deepcopy(old) + new['spec'][0]['variable'] = 'openstack_username' + resp = view._validate_spec_data(new, old) + assert resp.status_code == 400 + assert 'may not be used for new default' in str(resp.data['error']) + + def test_use_saved_encrypted_default(self): + ''' + Save is allowed, the $encrypted$ replacement is done + ''' + view = JobTemplateSurveySpec() + old = { + "name": "old survey", + "description": "foobar", + "spec": [ + { + "question_description": "", + "min": 0, + "default": "$encrypted$foooooooo", + "max": 1024, + "required": True, + "choices": "", + "variable": "openshift_username", + "question_name": "OpenShift Username", + "type": "password" + } + ] + } + new = deepcopy(old) + new['spec'][0]['default'] = '$encrypted$' + new['spec'][0]['required'] = False + resp = view._validate_spec_data(new, old) + assert resp is None + assert new == { + "name": "old survey", + "description": "foobar", + "spec": [ + { + "question_description": "", + "min": 0, + "default": "$encrypted$foooooooo", # default remained the same + "max": 1024, + "required": False, # only thing changed + "choices": "", + "variable": "openshift_username", + "question_name": "OpenShift Username", + "type": "password" + } + ] + } + + def test_use_saved_empty_string_default(self): + ''' + Save is allowed, the $encrypted$ replacement is done with empty string + The empty string value for default is unencrypted, + unlike all other password questions + ''' + view = JobTemplateSurveySpec() + old = { + "name": "old survey", + "description": "foobar", + "spec": [ + { + "question_description": "", + "min": 0, + "default": "", + "max": 1024, + "required": True, + "choices": "", + "variable": "openshift_username", + "question_name": "OpenShift Username", + "type": "password" + } + ] + } + new = deepcopy(old) + new['spec'][0]['default'] = '$encrypted$' + resp = view._validate_spec_data(new, old) + assert resp is None + assert new == { + "name": "old survey", + "description": "foobar", + "spec": [ + { + "question_description": "", + "min": 0, + "default": "", # still has old unencrypted default + "max": 1024, + "required": True, + "choices": "", + "variable": "openshift_username", + "question_name": "OpenShift Username", + "type": "password" + } + ] + } diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py index 0fff4eb5ca..e6388a890c 100644 --- a/awx/main/tests/unit/models/test_survey_models.py +++ b/awx/main/tests/unit/models/test_survey_models.py @@ -2,7 +2,7 @@ import tempfile import json import pytest - +from awx.main.utils.encryption import encrypt_value from awx.main.tasks import RunJob from awx.main.models import ( Job, @@ -10,6 +10,8 @@ from awx.main.models import ( WorkflowJobTemplate ) +ENCRYPTED_SECRET = encrypt_value('secret') + @pytest.mark.survey class SurveyVariableValidation: @@ -61,6 +63,7 @@ class SurveyVariableValidation: @pytest.fixture def job(mocker): ret = mocker.MagicMock(**{ + 'decrypted_extra_vars.return_value': '{\"secret_key\": \"my_password\"}', 'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}', 'extra_vars_dict': {"secret_key": "my_password"}, 'pk': 1, 'job_template.pk': 1, 'job_template.name': '', @@ -143,6 +146,15 @@ def test_update_kwargs_survey_invalid_default(survey_spec_factory): assert json.loads(defaulted_extra_vars['extra_vars'])['var2'] == 2 +@pytest.mark.survey +def test_display_survey_spec_encrypts_default(survey_spec_factory): + spec = survey_spec_factory('var2') + spec['spec'][0]['type'] = 'password' + spec['spec'][0]['default'] = 'some-default' + jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True) + assert jt.display_survey_spec()['spec'][0]['default'] == '$encrypted$' + + @pytest.mark.survey @pytest.mark.parametrize("question_type,default,min,max,expect_use,expect_value", [ ("text", "", 0, 0, True, ''), # default used @@ -183,6 +195,46 @@ def test_optional_survey_question_defaults( assert 'c' not in defaulted_extra_vars['extra_vars'] +@pytest.mark.survey +@pytest.mark.parametrize("question_type,default,maxlen,kwargs,expected", [ + ('text', None, 5, {}, {}), + ('text', '', 5, {}, {'x': ''}), + ('text', 'y', 5, {}, {'x': 'y'}), + ('text', 'too-long', 5, {}, {}), + ('password', None, 5, {}, {}), + ('password', '', 5, {}, {'x': ''}), + ('password', ENCRYPTED_SECRET, 5, {}, {}), # len(secret) == 6, invalid + ('password', ENCRYPTED_SECRET, 10, {}, {'x': ENCRYPTED_SECRET}), # len(secret) < 10, valid + ('password', None, 5, {'extra_vars': {'x': '$encrypted$'}}, {}), + ('password', '', 5, {'extra_vars': {'x': '$encrypted$'}}, {'x': ''}), + ('password', None, 5, {'extra_vars': {'x': 'y'}}, {'x': 'y'}), + ('password', '', 5, {'extra_vars': {'x': 'y'}}, {'x': 'y'}), + ('password', 'foo', 5, {'extra_vars': {'x': 'y'}}, {'x': 'y'}), + ('password', None, 5, {'extra_vars': {'x': ''}}, {'x': ''}), + ('password', '', 5, {'extra_vars': {'x': ''}}, {'x': ''}), + ('password', 'foo', 5, {'extra_vars': {'x': ''}}, {'x': ''}), + ('password', ENCRYPTED_SECRET, 5, {'extra_vars': {'x': '$encrypted$'}}, {}), + ('password', ENCRYPTED_SECRET, 10, {'extra_vars': {'x': '$encrypted$'}}, {'x': ENCRYPTED_SECRET}), +]) +def test_survey_encryption_defaults(survey_spec_factory, question_type, default, maxlen, kwargs, expected): + spec = survey_spec_factory([ + { + "required": True, + "variable": "x", + "min": 0, + "max": maxlen, + "type": question_type + }, + ]) + if default is not None: + spec['spec'][0]['default'] = default + else: + spec['spec'][0].pop('default', None) + jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True) + extra_vars = json.loads(jt._update_unified_job_kwargs({}, kwargs).get('extra_vars')) + assert extra_vars == expected + + @pytest.mark.survey class TestWorkflowSurveys: def test_update_kwargs_survey_defaults(self, survey_spec_factory): diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index cc18eb1c57..2f568a62a5 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -16,6 +16,7 @@ from django.conf import settings from awx.main.models import ( + AdHocCommand, Credential, CredentialType, Inventory, @@ -26,10 +27,11 @@ from awx.main.models import ( Project, ProjectUpdate, UnifiedJob, + User ) from awx.main import tasks -from awx.main.utils import encrypt_field +from awx.main.utils import encrypt_field, encrypt_value @@ -304,6 +306,32 @@ class TestGenericRun(TestJobExecution): assert '--ro-bind %s %s' % (settings.ANSIBLE_VENV_PATH, settings.ANSIBLE_VENV_PATH) in ' '.join(args) # noqa assert '--ro-bind %s %s' % (settings.AWX_VENV_PATH, settings.AWX_VENV_PATH) in ' '.join(args) # noqa + def test_created_by_extra_vars(self): + self.instance.created_by = User(pk=123, username='angry-spud') + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert '"tower_user_id": 123,' in ' '.join(args) + assert '"tower_user_name": "angry-spud"' in ' '.join(args) + assert '"awx_user_id": 123,' in ' '.join(args) + assert '"awx_user_name": "angry-spud"' in ' '.join(args) + + def test_survey_extra_vars(self): + self.instance.extra_vars = json.dumps({ + 'super_secret': encrypt_value('CLASSIFIED', pk=None) + }) + self.instance.survey_passwords = { + 'super_secret': '$encrypted$' + } + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert '"super_secret": "CLASSIFIED"' in ' '.join(args) + def test_awx_task_env(self): patch = mock.patch('awx.main.tasks.settings.AWX_TASK_ENV', {'FOO': 'BAR'}) patch.start() @@ -316,6 +344,35 @@ class TestGenericRun(TestJobExecution): assert env['FOO'] == 'BAR' +class TestAdhocRun(TestJobExecution): + + TASK_CLS = tasks.RunAdHocCommand + + def get_instance(self): + return AdHocCommand( + pk=1, + created=datetime.utcnow(), + inventory=Inventory(pk=1), + status='new', + cancel_flag=False, + verbosity=3, + extra_vars={'awx_foo': 'awx-bar'} + ) + + def test_created_by_extra_vars(self): + self.instance.created_by = User(pk=123, username='angry-spud') + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert '"tower_user_id": 123,' in ' '.join(args) + assert '"tower_user_name": "angry-spud"' in ' '.join(args) + assert '"awx_user_id": 123,' in ' '.join(args) + assert '"awx_user_name": "angry-spud"' in ' '.join(args) + assert '"awx_foo": "awx-bar' in ' '.join(args) + + class TestIsolatedExecution(TestJobExecution): REMOTE_HOST = 'some-isolated-host' @@ -670,7 +727,8 @@ class TestJobCredentials(TestJobExecution): inputs = { 'subscription': 'some-subscription', 'username': 'bob', - 'password': 'secret' + 'password': 'secret', + 'cloud_environment': 'foobar' } ) credential.inputs['password'] = encrypt_field(credential, 'password') @@ -685,6 +743,7 @@ class TestJobCredentials(TestJobExecution): assert env['AZURE_SUBSCRIPTION_ID'] == 'some-subscription' assert env['AZURE_AD_USER'] == 'bob' assert env['AZURE_PASSWORD'] == 'secret' + assert env['AZURE_CLOUD_ENVIRONMENT'] == 'foobar' def test_vmware_credentials(self): vmware = CredentialType.defaults['vmware']() @@ -738,6 +797,41 @@ class TestJobCredentials(TestJobExecution): self.run_pexpect.side_effect = run_pexpect_side_effect self.task.run(self.pk) + @pytest.mark.parametrize("ca_file", [None, '/path/to/some/file']) + def test_rhv_credentials(self, ca_file): + rhv = CredentialType.defaults['rhv']() + inputs = { + 'host': 'some-ovirt-host.example.org', + 'username': 'bob', + 'password': 'some-pass', + } + if ca_file: + inputs['ca_file'] = ca_file + credential = Credential( + pk=1, + credential_type=rhv, + inputs=inputs + ) + credential.inputs['password'] = encrypt_field(credential, 'password') + self.instance.extra_credentials.add(credential) + + def run_pexpect_side_effect(*args, **kwargs): + args, cwd, env, stdout = args + config = ConfigParser.ConfigParser() + config.read(env['OVIRT_INI_PATH']) + assert config.get('ovirt', 'ovirt_url') == 'some-ovirt-host.example.org' + assert config.get('ovirt', 'ovirt_username') == 'bob' + assert config.get('ovirt', 'ovirt_password') == 'some-pass' + if ca_file: + assert config.get('ovirt', 'ovirt_ca_file') == ca_file + else: + with pytest.raises(ConfigParser.NoOptionError): + config.get('ovirt', 'ovirt_ca_file') + return ['successful', 0] + + self.run_pexpect.side_effect = run_pexpect_side_effect + self.task.run(self.pk) + def test_net_credentials(self): net = CredentialType.defaults['net']() credential = Credential( @@ -1368,6 +1462,12 @@ class TestInventoryUpdateCredentials(TestJobExecution): assert env['GCE_ZONE'] == expected_gce_zone ssh_key_data = env['GCE_PEM_FILE_PATH'] assert open(ssh_key_data, 'rb').read() == self.EXAMPLE_PRIVATE_KEY + + config = ConfigParser.ConfigParser() + config.read(env['GCE_INI_PATH']) + assert 'cache' in config.sections() + assert config.getint('cache', 'cache_max_age') == 0 + return ['successful', 0] self.run_pexpect.side_effect = run_pexpect_side_effect diff --git a/awx/main/tests/unit/utils/test_encryption.py b/awx/main/tests/unit/utils/test_encryption.py index 29b68cc56b..b269c42b27 100644 --- a/awx/main/tests/unit/utils/test_encryption.py +++ b/awx/main/tests/unit/utils/test_encryption.py @@ -51,3 +51,24 @@ def test_encrypt_field_with_ask(): def test_encrypt_field_with_empty_value(): encrypted = encryption.encrypt_field(Setting(value=None), 'value') assert encrypted is None + + +class TestSurveyReversibilityValue: + ''' + Tests to enforce the contract with survey password question encrypted values + ''' + _key = encryption.get_encryption_key('value', None) + + def test_encrypt_empty_string(self): + assert encryption.encrypt_value('') == '' + # the reverse, decryption, does not work + + def test_encrypt_encryption_key(self): + assert encryption.encrypt_value('$encrypted$') == '$encrypted$' + # the reverse, decryption, does not work + + def test_encrypt_empty_string_twice(self): + # Encryption is idempotent + val = encryption.encrypt_value('foobar') + val2 = encryption.encrypt_value(val) + assert encryption.decrypt_value(self._key, val2) == 'foobar' diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 037661a1fe..b4cea8d4b7 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -729,6 +729,7 @@ def wrap_args_with_proot(args, cwd, **kwargs): - /var/log/supervisor ''' from django.conf import settings + cwd = os.path.realpath(cwd) new_args = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--unshare-pid', '--dev-bind', '/', '/'] hide_paths = [settings.AWX_PROOT_BASE_PATH] if not kwargs.get('isolated'): @@ -847,6 +848,7 @@ class OutputEventFilter(object): def __init__(self, fileobj=None, event_callback=None, raw_callback=None): self._fileobj = fileobj self._event_callback = event_callback + self._event_ct = 0 self._raw_callback = raw_callback self._counter = 1 self._start_line = 0 @@ -901,6 +903,7 @@ class OutputEventFilter(object): self._start_line += n_lines if self._event_callback: self._event_callback(event_data) + self._event_ct += 1 if next_event_data.get('uuid', None): self._current_event_data = next_event_data diff --git a/awx/main/utils/encryption.py b/awx/main/utils/encryption.py index 025081b773..c8c5b72afd 100644 --- a/awx/main/utils/encryption.py +++ b/awx/main/utils/encryption.py @@ -1,6 +1,7 @@ import base64 import hashlib import logging +from collections import namedtuple import six from cryptography.fernet import Fernet, InvalidToken @@ -8,7 +9,8 @@ from cryptography.hazmat.backends import default_backend from django.utils.encoding import smart_str -__all__ = ['get_encryption_key', 'encrypt_field', 'decrypt_field', 'decrypt_value'] +__all__ = ['get_encryption_key', 'encrypt_value', 'encrypt_field', + 'decrypt_field', 'decrypt_value'] logger = logging.getLogger('awx.main.utils.encryption') @@ -50,6 +52,11 @@ def get_encryption_key(field_name, pk=None): return base64.urlsafe_b64encode(h.digest()) +def encrypt_value(value, pk=None): + TransientField = namedtuple('TransientField', ['pk', 'value']) + return encrypt_field(TransientField(pk=pk, value=value), 'value') + + def encrypt_field(instance, field_name, ask=False, subfield=None, skip_utf8=False): ''' Return content of the given instance and field name encrypted. diff --git a/awx/main/utils/formatters.py b/awx/main/utils/formatters.py index 12034dd5ea..380641fdf9 100644 --- a/awx/main/utils/formatters.py +++ b/awx/main/utils/formatters.py @@ -120,6 +120,7 @@ class LogstashFormatter(LogstashFormatterVersion1): # exist if SQL_DEBUG is turned on in settings. headers = [ (float, 'X-API-Time'), # may end with an 's' "0.33s" + (float, 'X-API-Total-Time'), (int, 'X-API-Query-Count'), (float, 'X-API-Query-Time'), # may also end with an 's' (str, 'X-API-Node'), @@ -131,9 +132,11 @@ class LogstashFormatter(LogstashFormatterVersion1): 'path': request.path, 'path_info': request.path_info, 'query_string': request.META['QUERY_STRING'], - 'data': request.data, } + if hasattr(request, 'data'): + data_for_log['request']['data'] = request.data + return data_for_log def get_extra_fields(self, record): diff --git a/awx/playbooks/clean_isolated.yml b/awx/playbooks/clean_isolated.yml index e40e780396..f904abe5a8 100644 --- a/awx/playbooks/clean_isolated.yml +++ b/awx/playbooks/clean_isolated.yml @@ -16,4 +16,14 @@ - name: remove build artifacts file: path="{{item}}" state=absent + register: result with_items: "{{cleanup_dirs}}" + until: result|succeeded + ignore_errors: yes + retries: 3 + delay: 5 + + - name: fail if build artifacts were not cleaned + fail: + msg: 'Unable to cleanup build artifacts' + when: not result|succeeded diff --git a/awx/plugins/inventory/ovirt4.py b/awx/plugins/inventory/ovirt4.py new file mode 100755 index 0000000000..6221325f34 --- /dev/null +++ b/awx/plugins/inventory/ovirt4.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +""" +oVirt dynamic inventory script +================================= + +Generates dynamic inventory file for oVirt. + +Script will return following attributes for each virtual machine: + - id + - name + - host + - cluster + - status + - description + - fqdn + - os_type + - template + - tags + - statistics + - devices + +When run in --list mode, virtual machines are grouped by the following categories: + - cluster + - tag + - status + + Note: If there is some virtual machine which has has more tags it will be in both tag + records. + +Examples: + # Execute update of system on webserver virtual machine: + + $ ansible -i contrib/inventory/ovirt4.py webserver -m yum -a "name=* state=latest" + + # Get webserver virtual machine information: + + $ contrib/inventory/ovirt4.py --host webserver + +Author: Ondra Machacek (@machacekondra) +""" + +import argparse +import os +import sys + +from collections import defaultdict + +try: + import ConfigParser as configparser +except ImportError: + import configparser + +try: + import json +except ImportError: + import simplejson as json + +try: + import ovirtsdk4 as sdk + import ovirtsdk4.types as otypes +except ImportError: + print('oVirt inventory script requires ovirt-engine-sdk-python >= 4.0.0') + sys.exit(1) + + +def parse_args(): + """ + Create command line parser for oVirt dynamic inventory script. + """ + parser = argparse.ArgumentParser( + description='Ansible dynamic inventory script for oVirt.', + ) + parser.add_argument( + '--list', + action='store_true', + default=True, + help='Get data of all virtual machines (default: True).', + ) + parser.add_argument( + '--host', + help='Get data of virtual machines running on specified host.', + ) + parser.add_argument( + '--pretty', + action='store_true', + default=False, + help='Pretty format (default: False).', + ) + return parser.parse_args() + + +def create_connection(): + """ + Create a connection to oVirt engine API. + """ + # Get the path of the configuration file, by default use + # 'ovirt.ini' file in script directory: + default_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + 'ovirt.ini', + ) + config_path = os.environ.get('OVIRT_INI_PATH', default_path) + + # Create parser and add ovirt section if it doesn't exist: + config = configparser.SafeConfigParser( + defaults={ + 'ovirt_url': None, + 'ovirt_username': None, + 'ovirt_password': None, + 'ovirt_ca_file': None, + } + ) + if not config.has_section('ovirt'): + config.add_section('ovirt') + config.read(config_path) + + # Create a connection with options defined in ini file: + return sdk.Connection( + url=config.get('ovirt', 'ovirt_url'), + username=config.get('ovirt', 'ovirt_username'), + password=config.get('ovirt', 'ovirt_password'), + ca_file=config.get('ovirt', 'ovirt_ca_file'), + insecure=config.get('ovirt', 'ovirt_ca_file') is None, + ) + + +def get_dict_of_struct(connection, vm): + """ + Transform SDK Vm Struct type to Python dictionary. + """ + if vm is None: + return dict() + + vms_service = connection.system_service().vms_service() + clusters_service = connection.system_service().clusters_service() + vm_service = vms_service.vm_service(vm.id) + devices = vm_service.reported_devices_service().list() + tags = vm_service.tags_service().list() + stats = vm_service.statistics_service().list() + labels = vm_service.affinity_labels_service().list() + groups = clusters_service.cluster_service( + vm.cluster.id + ).affinity_groups_service().list() + + return { + 'id': vm.id, + 'name': vm.name, + 'host': connection.follow_link(vm.host).name if vm.host else None, + 'cluster': connection.follow_link(vm.cluster).name, + 'status': str(vm.status), + 'description': vm.description, + 'fqdn': vm.fqdn, + 'os_type': vm.os.type, + 'template': connection.follow_link(vm.template).name, + 'tags': [tag.name for tag in tags], + 'affinity_labels': [label.name for label in labels], + 'affinity_groups': [ + group.name for group in groups + if vm.name in [vm.name for vm in connection.follow_link(group.vms)] + ], + 'statistics': dict( + (stat.name, stat.values[0].datum) for stat in stats + ), + 'devices': dict( + (device.name, [ip.address for ip in device.ips]) for device in devices if device.ips + ), + 'ansible_host': next((device.ips[0].address for device in devices if device.ips), None) + } + + +def get_data(connection, vm_name=None): + """ + Obtain data of `vm_name` if specified, otherwise obtain data of all vms. + """ + vms_service = connection.system_service().vms_service() + clusters_service = connection.system_service().clusters_service() + + if vm_name: + vm = vms_service.list(search='name=%s' % vm_name) or [None] + data = get_dict_of_struct( + connection=connection, + vm=vm[0], + ) + else: + vms = dict() + data = defaultdict(list) + for vm in vms_service.list(): + name = vm.name + vm_service = vms_service.vm_service(vm.id) + cluster_service = clusters_service.cluster_service(vm.cluster.id) + + # Add vm to vms dict: + vms[name] = get_dict_of_struct(connection, vm) + + # Add vm to cluster group: + cluster_name = connection.follow_link(vm.cluster).name + data['cluster_%s' % cluster_name].append(name) + + # Add vm to tag group: + tags_service = vm_service.tags_service() + for tag in tags_service.list(): + data['tag_%s' % tag.name].append(name) + + # Add vm to status group: + data['status_%s' % vm.status].append(name) + + # Add vm to affinity group: + for group in cluster_service.affinity_groups_service().list(): + if vm.name in [ + v.name for v in connection.follow_link(group.vms) + ]: + data['affinity_group_%s' % group.name].append(vm.name) + + # Add vm to affinity label group: + affinity_labels_service = vm_service.affinity_labels_service() + for label in affinity_labels_service.list(): + data['affinity_label_%s' % label.name].append(name) + + data["_meta"] = { + 'hostvars': vms, + } + + return data + + +def main(): + args = parse_args() + connection = create_connection() + + print( + json.dumps( + obj=get_data( + connection=connection, + vm_name=args.host, + ), + sort_keys=args.pretty, + indent=args.pretty * 2, + ) + ) + +if __name__ == '__main__': + main() diff --git a/awx/plugins/inventory/tower.py b/awx/plugins/inventory/tower.py new file mode 100755 index 0000000000..63ade6b24b --- /dev/null +++ b/awx/plugins/inventory/tower.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +""" +Ansible Tower/AWX dynamic inventory script +========================================== + +Generates dynamic inventory for Tower + +Author: Matthew Jones (@matburt) +""" + +import argparse +import re +import os +import sys +import json +import requests +from requests.auth import HTTPBasicAuth +from urlparse import urljoin + + +def parse_configuration(): + """ + Create command line parser for oVirt dynamic inventory script. + """ + parser = argparse.ArgumentParser( + description='Ansible dynamic inventory script for Ansible Tower.', + ) + parser.add_argument( + '--list', + action='store_true', + default=True, + help='Return all hosts known to Tower given a particular inventory', + ) + parser.parse_args() + host_name = os.environ.get("TOWER_HOST", None) + username = os.environ.get("TOWER_USERNAME", None) + password = os.environ.get("TOWER_PASSWORD", None) + ignore_ssl = os.environ.get("TOWER_IGNORE_SSL", "1").lower() in ("1", "yes", "true") + inventory = os.environ.get("TOWER_INVENTORY", None) + license_type = os.environ.get("TOWER_LICENSE_TYPE", "enterprise") + + errors = [] + if not host_name: + errors.append("Missing TOWER_HOST in environment") + if not username: + errors.append("Missing TOWER_USERNAME in environment") + if not password: + errors.append("Missing TOWER_PASSWORD in environment") + if not inventory: + errors.append("Missing TOWER_INVENTORY in environment") + if errors: + raise RuntimeError("\n".join(errors)) + + return dict(tower_host=host_name, + tower_user=username, + tower_pass=password, + tower_inventory=inventory, + tower_license_type=license_type, + ignore_ssl=ignore_ssl) + + +def read_tower_inventory(tower_host, tower_user, tower_pass, inventory, license_type, ignore_ssl=False): + if not re.match('(?:http|https)://', tower_host): + tower_host = "https://{}".format(tower_host) + inventory_url = urljoin(tower_host, "/api/v2/inventories/{}/script/?hostvars=1&towervars=1&all=1".format(inventory.replace('/', ''))) + config_url = urljoin(tower_host, "/api/v2/config/") + try: + if license_type != "open": + config_response = requests.get(config_url, + auth=HTTPBasicAuth(tower_user, tower_pass), + verify=not ignore_ssl) + if config_response.ok: + source_type = config_response.json()['license_info']['license_type'] + if not source_type == license_type: + raise RuntimeError("Tower server licenses must match: source: {} local: {}".format(source_type, + license_type)) + else: + raise RuntimeError("Failed to validate the license of the remote Tower: {}".format(config_response.data)) + + response = requests.get(inventory_url, + auth=HTTPBasicAuth(tower_user, tower_pass), + verify=not ignore_ssl) + if response.ok: + return response.json() + json_reason = response.json() + reason = json_reason.get('detail', 'Retrieving Tower Inventory Failed') + except requests.ConnectionError, e: + reason = "Connection to remote host failed: {}".format(e) + except json.JSONDecodeError, e: + reason = "Failed to parse json from host: {}".format(e) + raise RuntimeError(reason) + + +def main(): + config = parse_configuration() + inventory_hosts = read_tower_inventory(config['tower_host'], + config['tower_user'], + config['tower_pass'], + config['tower_inventory'], + config['tower_license_type'], + ignore_ssl=config['ignore_ssl']) + print( + json.dumps( + inventory_hosts + ) + ) + + +if __name__ == '__main__': + main() diff --git a/awx/plugins/isolated/awx_capacity.py b/awx/plugins/isolated/awx_capacity.py index cc69d02a3b..cf370fb56e 100644 --- a/awx/plugins/isolated/awx_capacity.py +++ b/awx/plugins/isolated/awx_capacity.py @@ -41,7 +41,8 @@ def main(): total_mem_value = out.split()[7] if int(total_mem_value) <= 2048: cap = 50 - cap = 50 + ((int(total_mem_value) / 1024) - 2) * 75 + else: + cap = 50 + ((int(total_mem_value) / 1024) - 2) * 75 # Module never results in a change module.exit_json(changed=False, capacity=cap, version=version) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index f52aa36832..25d8073396 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -164,7 +164,7 @@ STDOUT_MAX_BYTES_DISPLAY = 1048576 # Returned in the header on event api lists as a recommendation to the UI # on how many events to display before truncating/hiding -RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER = 4000 +MAX_UI_JOB_EVENTS = 4000 # The maximum size of the ansible callback event's res data structure # beyond this limit and the value will be removed @@ -226,6 +226,7 @@ TEMPLATES = [ ] MIDDLEWARE_CLASSES = ( # NOQA + 'awx.main.middleware.TimingMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', @@ -517,7 +518,7 @@ SOCIAL_AUTH_INACTIVE_USER_URL = '/sso/inactive/' SOCIAL_AUTH_RAISE_EXCEPTIONS = False SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = False -SOCIAL_AUTH_SLUGIFY_USERNAMES = True +#SOCIAL_AUTH_SLUGIFY_USERNAMES = True SOCIAL_AUTH_CLEAN_USERNAMES = True SOCIAL_AUTH_SANITIZE_REDIRECTS = True @@ -819,6 +820,26 @@ OPENSTACK_HOST_FILTER = r'^.+$' OPENSTACK_EXCLUDE_EMPTY_GROUPS = True OPENSTACK_INSTANCE_ID_VAR = 'openstack.id' +# --------------------- +# ----- oVirt4 ----- +# --------------------- +RHV_ENABLED_VAR = 'status' +RHV_ENABLED_VALUE = 'up' +RHV_GROUP_FILTER = r'^.+$' +RHV_HOST_FILTER = r'^.+$' +RHV_EXCLUDE_EMPTY_GROUPS = True +RHV_INSTANCE_ID_VAR = 'id' + +# --------------------- +# ----- Tower ----- +# --------------------- +TOWER_ENABLED_VAR = 'remote_tower_enabled' +TOWER_ENABLED_VALUE = 'true' +TOWER_GROUP_FILTER = r'^.+$' +TOWER_HOST_FILTER = r'^.+$' +TOWER_EXCLUDE_EMPTY_GROUPS = True +TOWER_INSTANCE_ID_VAR = 'remote_tower_id' + # --------------------- # ----- Foreman ----- # --------------------- @@ -1122,8 +1143,12 @@ LOGGING = { }, } } +# Apply coloring to messages logged to the console COLOR_LOGS = False # https://github.com/django-polymorphic/django-polymorphic/issues/195 # FIXME: Disabling models.E006 warning until we can renamed Project and InventorySource SILENCED_SYSTEM_CHECKS = ['models.E006'] + +# Use middleware to get request statistics +AWX_REQUEST_PROFILE = False diff --git a/awx/sso/backends.py b/awx/sso/backends.py index b11844abd1..82a0dfe017 100644 --- a/awx/sso/backends.py +++ b/awx/sso/backends.py @@ -241,7 +241,8 @@ class TowerSAMLIdentityProvider(BaseSAMLIdentityProvider): """ key = self.conf.get(conf_key, default_attribute) value = attributes[key] if key in attributes else None - if isinstance(value, list): + # In certain implementations (like https://pagure.io/ipsilon) this value is a string, not a list + if isinstance(value, (list, tuple)): value = value[0] if conf_key in ('attr_first_name', 'attr_last_name', 'attr_username', 'attr_email') and value is None: logger.warn("Could not map user detail '%s' from SAML attribute '%s'; " @@ -309,7 +310,7 @@ def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=True): should_add = True if should_add: rel.add(user) - elif remove: + elif remove and user in rel.all(): rel.remove(user) diff --git a/awx/sso/tests/unit/test_ldap.py b/awx/sso/tests/unit/test_ldap.py index b4f036acf7..0a50871650 100644 --- a/awx/sso/tests/unit/test_ldap.py +++ b/awx/sso/tests/unit/test_ldap.py @@ -1,6 +1,7 @@ import ldap from awx.sso.backends import LDAPSettings +from awx.sso.validators import validate_ldap_filter def test_ldap_default_settings(mocker): @@ -15,4 +16,11 @@ def test_ldap_default_network_timeout(mocker): from_db = mocker.Mock(**{'order_by.return_value': []}) with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db): settings = LDAPSettings() - assert settings.CONNECTION_OPTIONS[ldap.OPT_NETWORK_TIMEOUT] == 30 + assert settings.CONNECTION_OPTIONS == { + ldap.OPT_REFERRALS: 0, + ldap.OPT_NETWORK_TIMEOUT: 30 + } + + +def test_ldap_filter_validator(): + validate_ldap_filter('(test-uid=%(user)s)', with_user=True) diff --git a/awx/sso/validators.py b/awx/sso/validators.py index dd1086a426..7e89958236 100644 --- a/awx/sso/validators.py +++ b/awx/sso/validators.py @@ -47,7 +47,7 @@ def validate_ldap_filter(value, with_user=False): dn_value = value.replace('%(user)s', 'USER') else: dn_value = value - if re.match(r'^\([A-Za-z0-9]+?=[^()]+?\)$', dn_value): + if re.match(r'^\([A-Za-z0-9-]+?=[^()]+?\)$', dn_value): return elif re.match(r'^\([&|!]\(.*?\)\)$', dn_value): try: diff --git a/awx/ui/client/legacy/styles/forms.less b/awx/ui/client/legacy/styles/forms.less index 4fb40e395d..9508b254c6 100644 --- a/awx/ui/client/legacy/styles/forms.less +++ b/awx/ui/client/legacy/styles/forms.less @@ -458,7 +458,6 @@ align-items: center; justify-content: center; border:1px solid @field-border; - border-right: 0px; } .Form-lookupButton:hover { @@ -470,7 +469,6 @@ .Form-lookupButton:active, .Form-lookupButton:focus { border: 1px solid @field-border; - border-right: 0px; } .CodeMirror { diff --git a/awx/ui/client/legacy/styles/jquery-ui-overrides.less b/awx/ui/client/legacy/styles/jquery-ui-overrides.less index 79f7dd5931..8656b3693f 100644 --- a/awx/ui/client/legacy/styles/jquery-ui-overrides.less +++ b/awx/ui/client/legacy/styles/jquery-ui-overrides.less @@ -149,7 +149,7 @@ table.ui-datepicker-calendar { .ui-widget-content { background-image: none; - background-color: @default-secondary-bg; + background-color: @default-bg; a, a:visited, diff --git a/awx/ui/client/legacy/styles/lists.less b/awx/ui/client/legacy/styles/lists.less index 466c8b75b1..1981c0c6de 100644 --- a/awx/ui/client/legacy/styles/lists.less +++ b/awx/ui/client/legacy/styles/lists.less @@ -248,8 +248,7 @@ table, tbody { } .List-buttonDefault[disabled] { - color: @d7grey; - border-color: @d7grey; + opacity: 0.65; } .List-searchDropdown { diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index e2e2e746a4..dc19c61e2a 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -72,7 +72,7 @@ function AtFormController (eventService, strings) { const data = vm.components .filter(component => component.category === 'input') .reduce((values, component) => { - if (!component.state._value) { + if (component.state._value === undefined) { return values; } diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 1ab6e941e7..1e6cc13588 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -84,6 +84,7 @@ function AtInputLookupController (baseInputController, $q, $state) { scope.state._touched = true; if (scope.state._displayValue === '' && !scope.state._required) { + scope.state._value = null; return vm.check({ isValid: true }); } diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index de53c7d938..81b1980fb1 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -62,13 +62,20 @@ function requestWithCache (config) { * @yields {boolean} - Indicating a match has been found. If so, the results * are set on the model. */ -function search (params, config) { +function search (params = {}, config = {}) { const req = { method: 'GET', - url: this.path, - params + url: this.path }; + if (typeof params === 'string') { + req.url = `?params`; + } else if (Array.isArray(params)) { + req.url += `?${params.join('&')}`; + } else { + req.params = params; + } + return $http(req) .then(({ data }) => { if (!data.count) { diff --git a/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js b/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js index 03a3fb0f38..5cd9e72a11 100644 --- a/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js +++ b/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js @@ -205,7 +205,8 @@ export default [ scope: $scope.$parent, variable: name, parse_variable: 'parseType', - field_id: form.formDef.name + '_' + name + field_id: form.formDef.name + '_' + name, + readOnly: $scope.$parent.configDataResolve[name] && $scope.$parent.configDataResolve[name].disabled ? true : false }); $scope.parseTypeChange('parseType', name); } diff --git a/awx/ui/client/src/credential-types/add/add.controller.js b/awx/ui/client/src/credential-types/add/add.controller.js index ca8519395a..1e01e16736 100644 --- a/awx/ui/client/src/credential-types/add/add.controller.js +++ b/awx/ui/client/src/credential-types/add/add.controller.js @@ -37,8 +37,14 @@ export default ['Rest', 'Wait', callback: 'loadCredentialKindOptions' }); - $scope.inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs"); - $scope.injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector"); + const docs_url = 'https://docs.ansible.com/ansible-tower/latest/html/userguide/credential_types.html#getting-started-with-credential-types'; + const docs_help_text = `

Getting Started with Credential Types`; + + const api_inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs."); + const api_injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector."); + + $scope.inputs_help_text = api_inputs_help_text + docs_help_text; + $scope.injectors_help_text = api_injectors_help_text + docs_help_text; if (!options.actions.POST) { $state.go("^"); diff --git a/awx/ui/client/src/credential-types/edit/edit.controller.js b/awx/ui/client/src/credential-types/edit/edit.controller.js index 14745dcca7..2fffb31fd9 100644 --- a/awx/ui/client/src/credential-types/edit/edit.controller.js +++ b/awx/ui/client/src/credential-types/edit/edit.controller.js @@ -36,8 +36,14 @@ export default ['Rest', 'Wait', callback: 'choicesReadyCredentialTypes' }); - $scope.inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs"); - $scope.injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector"); + const docs_url = 'https://docs.ansible.com/ansible-tower/latest/html/userguide/credential_types.html#getting-started-with-credential-types'; + const docs_help_text = `

Getting Started with Credential Types`; + + const api_inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs."); + const api_injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector."); + + $scope.inputs_help_text = api_inputs_help_text + docs_help_text; + $scope.injectors_help_text = api_injectors_help_text + docs_help_text; }); } diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js index 658bbdaecb..6222f37000 100644 --- a/awx/ui/client/src/credentials/list/credentials-list.controller.js +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -11,7 +11,7 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset, credentialType, i18n, Credential, CredentialsStrings) { - let credential = new Credential(); + const credential = new Credential(); var list = CredentialList, defaultUrl = GetBasePath('credentials'); @@ -48,9 +48,25 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' return; } - $scope[list.name].forEach(credential => { - credential.kind = credentialType.match('id', credential.credential_type).name; - }); + const params = $scope[list.name] + .reduce((accumulator, credential) => { + accumulator.push(credential.credential_type); + + return accumulator; + }, []) + .filter((id, i, array) => array.indexOf(id) === i) + .map(id => `or__id=${id}`); + + credentialType.search(params) + .then(found => { + if (!found) { + return; + } + + $scope[list.name].forEach(credential => { + credential.kind = credentialType.match('id', credential.credential_type).name; + }); + }); } // iterate over the list and add fields like type label, after the diff --git a/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js b/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js index 23a4489b4b..b97ea561ae 100644 --- a/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js +++ b/awx/ui/client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js @@ -9,11 +9,12 @@ 'adjustGraphSize', 'templateUrl', 'i18n', + 'moment', 'jobStatusGraphData', JobStatusGraph ]; -function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataService) { +function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, moment, graphDataService) { return { restrict: 'E', scope: { @@ -72,11 +73,11 @@ function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataSe } }); - if(period==="day") { - timeFormat="%H:%M"; + if(period === "day") { + timeFormat="H:M"; } else { - timeFormat = '%m/%d'; + timeFormat = "MMM D"; } graphData.map(function(series) { series.values = series.values.map(function(d) { @@ -93,7 +94,8 @@ function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataSe .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! .showLegend(false) //Show the legend, allowing users to turn on/off line series. .showYAxis(true) //Show the y-axis - .showXAxis(true); //Show the x-axis + .showXAxis(true) //Show the x-axis + .margin({ right: 32 }); job_status_chart.interactiveLayer.tooltip.fixedTop(-10); //distance from the top of the chart to tooltip job_status_chart.interactiveLayer.tooltip.distance(-1); //distance from interactive line to tooltip @@ -101,8 +103,15 @@ function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataSe job_status_chart.xAxis .axisLabel(i18n._("TIME"))//.showMaxMin(true) .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; + const dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + + if (!dx) { + return ''; + } + + const tickDate = new Date(Number(dx + '000')); + + return moment(tickDate).format(timeFormat); }); job_status_chart.yAxis //Chart y-axis settings diff --git a/awx/ui/client/src/inventories-hosts/hosts/host.list.js b/awx/ui/client/src/inventories-hosts/hosts/host.list.js index e91eebb30b..2422666878 100644 --- a/awx/ui/client/src/inventories-hosts/hosts/host.list.js +++ b/awx/ui/client/src/inventories-hosts/hosts/host.list.js @@ -104,12 +104,14 @@ export default ['i18n', function(i18n) { smart_inventory: { mode: 'all', ngClick: "smartInventory()", - awToolTip: i18n._("Create a new Smart Inventory from search results."), + awToolTip: "{{ smartInventoryButtonTooltip }}", + dataTipWatch: 'smartInventoryButtonTooltip', actionClass: 'btn List-buttonDefault', buttonContent: i18n._('SMART INVENTORY'), ngShow: 'canAdd && (hosts.length > 0 || !(searchTags | isEmpty))', dataPlacement: "top", - ngDisabled: '!enableSmartInventoryButton' + ngDisabled: '!enableSmartInventoryButton', + showTipWhenDisabled: true } } }; diff --git a/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js b/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js index 643e52c9b8..0755149c16 100644 --- a/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js @@ -7,7 +7,7 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, rbacUiControlService, Dataset, $state, $filter, Prompt, Wait, - HostsService, SetStatus, canAdd, $transitions) { + HostsService, SetStatus, canAdd, $transitions, InventoryHostsStrings, HostsList) { let list = HostsList; @@ -16,6 +16,7 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, function init(){ $scope.canAdd = canAdd; $scope.enableSmartInventoryButton = false; + $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); // Search init $scope.list = list; @@ -37,14 +38,16 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, if(trans.params('to') && trans.params('to').host_search) { let hasMoreThanDefaultKeys = false; angular.forEach(trans.params('to').host_search, function(value, key) { - if(key !== 'order_by' && key !== 'page_size') { + if(key !== 'order_by' && key !== 'page_size' && key !== 'page') { hasMoreThanDefaultKeys = true; } }); $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; + $scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); } else { $scope.enableSmartInventoryButton = false; + $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); } }); @@ -83,20 +86,7 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, }; $scope.smartInventory = function() { - // Gather up search terms and pass them to the add smart inventory form - let stateParamsCopy = angular.copy($state.params.host_search); - let defaults = _.find($state.$current.path, (step) => { - if(step && step.params && step.params.hasOwnProperty(`host_search`)){ - return step.params.hasOwnProperty(`host_search`); - } - }).params[`host_search`].config.value; - - // Strip defaults out of the state params copy - angular.forEach(Object.keys(defaults), function(value) { - delete stateParamsCopy[value]; - }); - - $state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify(stateParamsCopy)}); + $state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify({"host_filter":`${$state.params.host_search.host_filter}`})}); }; $scope.editInventory = function(host) { @@ -114,5 +104,5 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath', 'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait', - 'HostsService', 'SetStatus', 'canAdd', '$transitions', HostsList + 'HostsService', 'SetStatus', 'canAdd', '$transitions', 'InventoryHostsStrings', HostsList ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js index bf9842bd99..120d09567b 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js @@ -141,9 +141,11 @@ } else { $state.go($state.current, reloadListStateParams, {reload: true}); } - $('#group-delete-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); + setTimeout(function(){ + $('#group-delete-modal').modal('hide'); + $('body').removeClass('modal-open'); + $('.modal-backdrop').remove(); + }, 1000); }); break; default: @@ -153,9 +155,11 @@ } else { $state.go($state.current, reloadListStateParams, {reload: true}); } - $('#group-delete-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); + setTimeout(function(){ + $('#group-delete-modal').modal('hide'); + $('body').removeClass('modal-open'); + $('.modal-backdrop').remove(); + }, 1000); }); } }; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html index ec594610a6..23579b2285 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html @@ -5,7 +5,7 @@