mirror of
https://github.com/ansible/awx.git
synced 2026-07-17 19:31:55 -02:30
Compare commits
15 Commits
forward-po
...
AAP-77251
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6df41d11e3 | ||
|
|
1603d4513a | ||
|
|
88d92d6231 | ||
|
|
354fa35860 | ||
|
|
d0576d7823 | ||
|
|
9d5bf22f90 | ||
|
|
f3b04125a6 | ||
|
|
1bd07b981a | ||
|
|
41545cfcf0 | ||
|
|
72a1922a9c | ||
|
|
f8fa690de3 | ||
|
|
8ab5deb54a | ||
|
|
843f23f4cb | ||
|
|
6d665dda33 | ||
|
|
17dc7f898a |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -260,7 +260,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set +e
|
||||
timeout 15m bash -elc '
|
||||
timeout 20m bash -elc '
|
||||
python -m pip install -r molecule/requirements.txt
|
||||
python -m pip install PyYAML # for awx/tools/scripts/rewrite-awx-operator-requirements.py
|
||||
$(realpath ../awx/tools/scripts/rewrite-awx-operator-requirements.py) molecule/requirements.yml $(realpath ../awx)
|
||||
|
||||
@@ -961,14 +961,27 @@ class UnifiedJobSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||
OPTIONAL_EXCLUDE_FIELDS = frozenset({'artifacts', 'extra_vars'})
|
||||
|
||||
_ALWAYS_STRIPPED_FIELDS = frozenset({'job_args', 'job_cwd', 'job_env', 'result_traceback', 'event_processing_finished'})
|
||||
|
||||
class Meta:
|
||||
fields = ('*', '-job_args', '-job_cwd', '-job_env', '-result_traceback', '-event_processing_finished', '-artifacts')
|
||||
fields = ('*', '-job_args', '-job_cwd', '-job_env', '-result_traceback', '-event_processing_finished')
|
||||
|
||||
def _requested_excludes(self):
|
||||
request = self.context.get('request')
|
||||
if request is None:
|
||||
return frozenset()
|
||||
raw = request.query_params.get('exclude', '')
|
||||
requested = {name.strip() for name in raw.split(',') if name.strip()}
|
||||
return frozenset(requested) & self.OPTIONAL_EXCLUDE_FIELDS
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
field_names = super(UnifiedJobListSerializer, self).get_field_names(declared_fields, info)
|
||||
# Meta multiple inheritance and -field_name options don't seem to be
|
||||
# taking effect above, so remove the undesired fields here.
|
||||
return tuple(x for x in field_names if x not in ('job_args', 'job_cwd', 'job_env', 'result_traceback', 'event_processing_finished', 'artifacts'))
|
||||
strip = self._ALWAYS_STRIPPED_FIELDS | self._requested_excludes()
|
||||
return tuple(x for x in field_names if x not in strip)
|
||||
|
||||
def get_types(self):
|
||||
if type(self) is UnifiedJobListSerializer:
|
||||
|
||||
@@ -127,6 +127,7 @@ from awx.api.views.mixin import (
|
||||
RelatedJobsPreventDeleteMixin,
|
||||
UnifiedJobDeletionMixin,
|
||||
NoTruncateMixin,
|
||||
UnifiedJobExcludeMixin,
|
||||
)
|
||||
from awx.api.pagination import UnifiedJobEventPagination
|
||||
from awx.main.utils import set_environ
|
||||
@@ -1926,7 +1927,8 @@ class HostList(HostRelatedSearchMixin, ListCreateAPIView):
|
||||
if filter_string:
|
||||
filter_qs = SmartFilter.query_from_string(filter_string)
|
||||
qs &= filter_qs
|
||||
return qs.distinct().with_latest_summary_id()
|
||||
qs = qs.distinct()
|
||||
return qs.with_latest_summary_id()
|
||||
|
||||
def list(self, *args, **kwargs):
|
||||
try:
|
||||
@@ -3850,7 +3852,7 @@ class SystemJobTemplateNotificationTemplatesSuccessList(SystemJobTemplateNotific
|
||||
resource_purpose = 'notification templates triggered on system job success'
|
||||
|
||||
|
||||
class JobList(ListAPIView):
|
||||
class JobList(UnifiedJobExcludeMixin, ListAPIView):
|
||||
model = models.Job
|
||||
serializer_class = serializers.JobListSerializer
|
||||
resource_purpose = 'jobs'
|
||||
@@ -4567,7 +4569,7 @@ class UnifiedJobTemplateList(ListAPIView):
|
||||
resource_purpose = 'unified job templates'
|
||||
|
||||
|
||||
class UnifiedJobList(ListAPIView):
|
||||
class UnifiedJobList(UnifiedJobExcludeMixin, ListAPIView):
|
||||
model = models.UnifiedJob
|
||||
serializer_class = serializers.UnifiedJobListSerializer
|
||||
search_fields = ('description', 'name', 'job__playbook')
|
||||
|
||||
@@ -233,3 +233,9 @@ class NoTruncateMixin(object):
|
||||
if self.request.query_params.get('no_truncate'):
|
||||
context.update(no_truncate=True)
|
||||
return context
|
||||
|
||||
|
||||
class UnifiedJobExcludeMixin(object):
|
||||
# Reserve the name 'exclude' so we can use it as a query param. Otherwise, the rest-filters backend
|
||||
# would treat it as a model field lookup.
|
||||
rest_filters_reserved_names = ('exclude',)
|
||||
|
||||
@@ -1665,11 +1665,11 @@ class JobAccess(BaseAccess):
|
||||
def filtered_queryset(self):
|
||||
qs = self.model.objects
|
||||
|
||||
qs_jt = qs.filter(job_template__in=JobTemplate.access_qs(self.user, 'view'))
|
||||
|
||||
org_access_qs = Organization.objects.filter(Q(admin_role__members=self.user) | Q(auditor_role__members=self.user))
|
||||
org_access_qs = Organization.objects.filter(
|
||||
Q(pk__in=Organization.access_ids_qs(self.user, 'change')) | Q(pk__in=Organization.access_ids_qs(self.user, 'audit_organization'))
|
||||
)
|
||||
if not org_access_qs.exists():
|
||||
return qs_jt
|
||||
return qs.filter(job_template__in=JobTemplate.access_qs(self.user, 'view'))
|
||||
|
||||
return qs.filter(Q(job_template__in=JobTemplate.access_qs(self.user, 'view')) | Q(organization__in=org_access_qs)).distinct()
|
||||
|
||||
@@ -2309,7 +2309,7 @@ class JobHostSummaryAccess(BaseAccess):
|
||||
|
||||
class JobEventAccess(BaseAccess):
|
||||
"""
|
||||
I can see job event records whenever I can read both job and host.
|
||||
I can see job event records whenever I can read the job or the host.
|
||||
"""
|
||||
|
||||
model = JobEvent
|
||||
@@ -2320,8 +2320,8 @@ class JobEventAccess(BaseAccess):
|
||||
|
||||
def filtered_queryset(self):
|
||||
return self.model.objects.filter(
|
||||
Q(host__inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
|
||||
| Q(job__job_template__in=JobTemplate.accessible_pk_qs(self.user, 'read_role'))
|
||||
Q(host_id__in=Host.objects.filter(inventory__in=Inventory.access_ids_qs(self.user, 'view')).values('pk'))
|
||||
| Q(job_id__in=Job.objects.filter(job_template__in=JobTemplate.access_ids_qs(self.user, 'view')).values('pk'))
|
||||
)
|
||||
|
||||
def can_add(self, data):
|
||||
@@ -2451,7 +2451,11 @@ class UnifiedJobTemplateAccess(BaseAccess):
|
||||
def filtered_queryset(self):
|
||||
return self.model.objects.filter(
|
||||
Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role'))
|
||||
| Q(inventorysource__inventory__id__in=Inventory._accessible_pk_qs(Inventory, self.user, 'read_role'))
|
||||
| Q(
|
||||
pk__in=InventorySource.objects.filter(
|
||||
inventory__id__in=Inventory.access_ids_qs(self.user, 'view'),
|
||||
).values('unifiedjobtemplate_ptr_id')
|
||||
)
|
||||
)
|
||||
|
||||
def can_start(self, obj, validate_license=True):
|
||||
@@ -2497,12 +2501,20 @@ class UnifiedJobAccess(BaseAccess):
|
||||
# )
|
||||
|
||||
def filtered_queryset(self):
|
||||
inv_pk_qs = Inventory._accessible_pk_qs(Inventory, self.user, 'read_role')
|
||||
inv_pk_qs = Inventory.access_ids_qs(self.user, 'view')
|
||||
qs = self.model.objects.filter(
|
||||
Q(unified_job_template_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'))
|
||||
| Q(inventoryupdate__inventory_source__inventory__id__in=inv_pk_qs)
|
||||
| Q(adhoccommand__inventory__id__in=inv_pk_qs)
|
||||
| Q(organization__in=Organization.accessible_pk_qs(self.user, 'auditor_role'))
|
||||
| Q(
|
||||
pk__in=InventoryUpdate.objects.filter(
|
||||
inventory_source__inventory__id__in=inv_pk_qs,
|
||||
).values('pk')
|
||||
)
|
||||
| Q(
|
||||
pk__in=AdHocCommand.objects.filter(
|
||||
inventory__id__in=inv_pk_qs,
|
||||
).values('pk')
|
||||
)
|
||||
| Q(organization__in=Organization.access_ids_qs(self.user, 'audit_organization'))
|
||||
)
|
||||
return qs
|
||||
|
||||
@@ -2622,9 +2634,13 @@ class LabelAccess(BaseAccess):
|
||||
|
||||
def filtered_queryset(self):
|
||||
return self.model.objects.filter(
|
||||
Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role'))
|
||||
| Q(unifiedjobtemplate_labels__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'))
|
||||
).distinct()
|
||||
Q(organization__in=Organization.access_ids_qs(self.user, 'view'))
|
||||
| Q(
|
||||
pk__in=UnifiedJobTemplate.labels.through.objects.filter(
|
||||
unifiedjobtemplate_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'),
|
||||
).values('label_id')
|
||||
)
|
||||
)
|
||||
|
||||
@check_superuser
|
||||
def can_add(self, data):
|
||||
@@ -2698,54 +2714,73 @@ class ActivityStreamAccess(BaseAccess):
|
||||
# 'job_template', 'job', 'project', 'project_update', 'workflow_job',
|
||||
# 'inventory_source', 'workflow_job_template'
|
||||
|
||||
q = Q(user=self.user)
|
||||
inventory_set = Inventory.accessible_pk_qs(self.user, 'read_role')
|
||||
if inventory_set:
|
||||
AS = ActivityStream
|
||||
|
||||
q = Q(pk__in=AS.user.through.objects.filter(user=self.user).values('activitystream_id'))
|
||||
|
||||
inventory_set = Inventory.access_ids_qs(self.user, 'view')
|
||||
if inventory_set.exists():
|
||||
q |= (
|
||||
Q(ad_hoc_command__inventory__in=inventory_set)
|
||||
| Q(inventory__in=inventory_set)
|
||||
| Q(host__inventory__in=inventory_set)
|
||||
| Q(group__inventory__in=inventory_set)
|
||||
| Q(inventory_source__inventory__in=inventory_set)
|
||||
| Q(inventory_update__inventory_source__inventory__in=inventory_set)
|
||||
Q(pk__in=AS.ad_hoc_command.through.objects.filter(adhoccommand__inventory__in=inventory_set).values('activitystream_id'))
|
||||
| Q(pk__in=AS.inventory.through.objects.filter(inventory__in=inventory_set).values('activitystream_id'))
|
||||
| Q(pk__in=AS.host.through.objects.filter(host__inventory__in=inventory_set).values('activitystream_id'))
|
||||
| Q(pk__in=AS.group.through.objects.filter(group__inventory__in=inventory_set).values('activitystream_id'))
|
||||
| Q(pk__in=AS.inventory_source.through.objects.filter(inventorysource__inventory__in=inventory_set).values('activitystream_id'))
|
||||
| Q(
|
||||
pk__in=AS.inventory_update.through.objects.filter(inventoryupdate__inventory_source__inventory__in=inventory_set).values(
|
||||
'activitystream_id'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
credential_set = Credential.accessible_pk_qs(self.user, 'read_role')
|
||||
if credential_set:
|
||||
q |= Q(credential__in=credential_set)
|
||||
credential_set = Credential.access_ids_qs(self.user, 'view')
|
||||
if credential_set.exists():
|
||||
q |= Q(pk__in=AS.credential.through.objects.filter(credential__in=credential_set).values('activitystream_id'))
|
||||
|
||||
auditing_orgs = (Organization.access_qs(self.user, 'change') | Organization.access_qs(self.user, 'audit')).distinct().values_list('id', flat=True)
|
||||
if auditing_orgs:
|
||||
if auditing_orgs.exists():
|
||||
q |= (
|
||||
Q(user__in=auditing_orgs.values('member_role__members'))
|
||||
| Q(organization__in=auditing_orgs)
|
||||
| Q(notification_template__organization__in=auditing_orgs)
|
||||
| Q(notification__notification_template__organization__in=auditing_orgs)
|
||||
| Q(label__organization__in=auditing_orgs)
|
||||
| Q(role__in=Role.visible_roles(self.user) if auditing_orgs else [])
|
||||
Q(pk__in=AS.user.through.objects.filter(user__in=auditing_orgs.values('member_role__members')).values('activitystream_id'))
|
||||
| Q(pk__in=AS.organization.through.objects.filter(organization__in=auditing_orgs).values('activitystream_id'))
|
||||
| Q(pk__in=AS.notification_template.through.objects.filter(notificationtemplate__organization__in=auditing_orgs).values('activitystream_id'))
|
||||
| Q(
|
||||
pk__in=AS.notification.through.objects.filter(notification__notification_template__organization__in=auditing_orgs).values(
|
||||
'activitystream_id'
|
||||
)
|
||||
)
|
||||
| Q(pk__in=AS.label.through.objects.filter(label__organization__in=auditing_orgs).values('activitystream_id'))
|
||||
| Q(pk__in=AS.role.through.objects.filter(role__in=Role.visible_roles(self.user)).values('activitystream_id'))
|
||||
)
|
||||
|
||||
project_set = Project.accessible_pk_qs(self.user, 'read_role')
|
||||
if project_set:
|
||||
q |= Q(project__in=project_set) | Q(project_update__project__in=project_set)
|
||||
|
||||
jt_set = JobTemplate.accessible_pk_qs(self.user, 'read_role')
|
||||
if jt_set:
|
||||
q |= Q(job_template__in=jt_set) | Q(job__job_template__in=jt_set)
|
||||
|
||||
wfjt_set = WorkflowJobTemplate.accessible_pk_qs(self.user, 'read_role')
|
||||
if wfjt_set:
|
||||
q |= (
|
||||
Q(workflow_job_template__in=wfjt_set)
|
||||
| Q(workflow_job_template_node__workflow_job_template__in=wfjt_set)
|
||||
| Q(workflow_job__workflow_job_template__in=wfjt_set)
|
||||
project_set = Project.access_ids_qs(self.user, 'view')
|
||||
if project_set.exists():
|
||||
q |= Q(pk__in=AS.project.through.objects.filter(project__in=project_set).values('activitystream_id')) | Q(
|
||||
pk__in=AS.project_update.through.objects.filter(projectupdate__project__in=project_set).values('activitystream_id')
|
||||
)
|
||||
|
||||
team_set = Team.accessible_pk_qs(self.user, 'read_role')
|
||||
if team_set:
|
||||
q |= Q(team__in=team_set)
|
||||
jt_set = JobTemplate.access_ids_qs(self.user, 'view')
|
||||
if jt_set.exists():
|
||||
q |= Q(pk__in=AS.job_template.through.objects.filter(jobtemplate__in=jt_set).values('activitystream_id')) | Q(
|
||||
pk__in=AS.job.through.objects.filter(job__job_template__in=jt_set).values('activitystream_id')
|
||||
)
|
||||
|
||||
return qs.filter(q).distinct()
|
||||
wfjt_set = WorkflowJobTemplate.access_ids_qs(self.user, 'view')
|
||||
if wfjt_set.exists():
|
||||
q |= (
|
||||
Q(pk__in=AS.workflow_job_template.through.objects.filter(workflowjobtemplate__in=wfjt_set).values('activitystream_id'))
|
||||
| Q(
|
||||
pk__in=AS.workflow_job_template_node.through.objects.filter(workflowjobtemplatenode__workflow_job_template__in=wfjt_set).values(
|
||||
'activitystream_id'
|
||||
)
|
||||
)
|
||||
| Q(pk__in=AS.workflow_job.through.objects.filter(workflowjob__workflow_job_template__in=wfjt_set).values('activitystream_id'))
|
||||
)
|
||||
|
||||
team_set = Team.access_ids_qs(self.user, 'view')
|
||||
if team_set.exists():
|
||||
q |= Q(pk__in=AS.team.through.objects.filter(team__in=team_set).values('activitystream_id'))
|
||||
|
||||
return qs.filter(q)
|
||||
|
||||
def can_add(self, data):
|
||||
return False
|
||||
|
||||
@@ -68,7 +68,7 @@ class RecordedQueryLog(object):
|
||||
progname = match
|
||||
break
|
||||
else:
|
||||
progname = os.path.basename(sys.argv[0])
|
||||
progname = 'unknown'
|
||||
filepath = os.path.join(self.dest, '{}.sqlite'.format(progname))
|
||||
version = _get_version('awx')
|
||||
log = sqlite3.connect(filepath, timeout=3)
|
||||
|
||||
17
awx/main/migrations/0206_jobhostsummary_host_id_idx.py
Normal file
17
awx/main/migrations/0206_jobhostsummary_host_id_idx.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('main', '0205_add_ordering_to_instancegroup_and_workflow_nodes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='jobhostsummary',
|
||||
index=models.Index(
|
||||
fields=['host', '-id'],
|
||||
name='main_jobhostsumm_host_id_desc',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1092,6 +1092,9 @@ class JobHostSummary(CreatedModifiedModel):
|
||||
unique_together = [('job', 'host_name')]
|
||||
verbose_name_plural = _('job host summaries')
|
||||
ordering = ('-pk',)
|
||||
indexes = [
|
||||
models.Index(fields=['host', '-id'], name='main_jobhostsumm_host_id_desc'),
|
||||
]
|
||||
|
||||
job = models.ForeignKey(
|
||||
'Job',
|
||||
|
||||
@@ -20,6 +20,9 @@ from dispatcherd.factories import get_control_from_settings
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.db import models, connection, transaction
|
||||
|
||||
# psycopg
|
||||
from psycopg import sql
|
||||
from django.db.models.constraints import UniqueConstraint
|
||||
from django.core.exceptions import NON_FIELD_ERRORS
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -1179,17 +1182,23 @@ class UnifiedJob(
|
||||
raise StdoutMaxBytesExceeded(total, max_supported)
|
||||
|
||||
tbl = self._meta.db_table + 'event'
|
||||
created_by_cond = ''
|
||||
where_parts = [
|
||||
sql.SQL('{} = {}').format(sql.Identifier(self.event_parent_key), sql.Literal(self.id)),
|
||||
sql.SQL("stdout != ''"),
|
||||
]
|
||||
if self.has_unpartitioned_events:
|
||||
tbl = f'_unpartitioned_{tbl}'
|
||||
tbl = '_unpartitioned_' + tbl
|
||||
else:
|
||||
created_by_cond = f"job_created='{self.created.isoformat()}' AND "
|
||||
where_parts.insert(0, sql.SQL('job_created = {}').format(sql.Literal(self.created)))
|
||||
|
||||
sql = f"copy (select stdout from {tbl} where {created_by_cond}{self.event_parent_key}={self.id} and stdout != '' order by start_line) to stdout" # nosql
|
||||
copy_sql = sql.SQL('COPY (SELECT stdout FROM {} WHERE {} ORDER BY start_line) TO STDOUT').format(
|
||||
sql.Identifier(tbl),
|
||||
sql.SQL(' AND ').join(where_parts),
|
||||
)
|
||||
# psycopg3's copy writes bytes, but callers of this
|
||||
# function assume a str-based fd will be returned; decode
|
||||
# .write() calls on the fly to maintain this interface
|
||||
with cursor.copy(sql) as copy:
|
||||
with cursor.copy(copy_sql) as copy:
|
||||
while data := copy.read():
|
||||
fd.write(smart_str(bytes(data)))
|
||||
|
||||
|
||||
@@ -1022,6 +1022,33 @@ def update_host_smart_inventory_memberships():
|
||||
smart_inventory.update_computed_fields()
|
||||
|
||||
|
||||
def _batched_delete_inventory(inventory, batch_size=500):
|
||||
"""Delete inventory hosts in batches to avoid high memory usage.
|
||||
|
||||
With ansible facts, loading thousands of hosts at once can use a lot of memory. To avoid
|
||||
this, we delete them in batches (of 500).
|
||||
|
||||
Safe to retry after a crash because inventory.pending_deletion
|
||||
is already set and each batch is its own transaction.
|
||||
"""
|
||||
from awx.main.models.inventory import Host
|
||||
|
||||
# first delete all hosts in batches
|
||||
total_deleted = 0
|
||||
while True:
|
||||
pks = list(Host.objects.filter(inventory_id=inventory.id).values_list('pk', flat=True)[:batch_size])
|
||||
if not pks:
|
||||
break
|
||||
with transaction.atomic():
|
||||
deleted_count, _ = Host.objects.filter(pk__in=pks).delete()
|
||||
total_deleted += deleted_count
|
||||
logger.debug('Batch-deleted %d hosts from inventory %d (%d total so far)', len(pks), inventory.id, total_deleted)
|
||||
|
||||
# then delete the inventory itself
|
||||
inventory.delete()
|
||||
logger.info('Batched deletion of inventory %d complete (%d hosts removed)', inventory.id, total_deleted)
|
||||
|
||||
|
||||
@task(queue=get_task_queuename, timeout=3600 * 5)
|
||||
def delete_inventory(inventory_id, user_id, retries=5):
|
||||
# Delete inventory as user
|
||||
@@ -1034,12 +1061,14 @@ def delete_inventory(inventory_id, user_id, retries=5):
|
||||
user = None
|
||||
with ignore_inventory_computed_fields(), ignore_inventory_group_removal(), impersonate(user):
|
||||
try:
|
||||
Inventory.objects.get(id=inventory_id).delete()
|
||||
emit_channel_notification('inventories-status_changed', {'group_name': 'inventories', 'inventory_id': inventory_id, 'status': 'deleted'})
|
||||
logger.debug('Deleted inventory {} as user {}.'.format(inventory_id, user_id))
|
||||
inv = Inventory.objects.get(id=inventory_id)
|
||||
except Inventory.DoesNotExist:
|
||||
logger.exception("Delete Inventory failed due to missing inventory: " + str(inventory_id))
|
||||
return
|
||||
try:
|
||||
_batched_delete_inventory(inv)
|
||||
emit_channel_notification('inventories-status_changed', {'group_name': 'inventories', 'inventory_id': inventory_id, 'status': 'deleted'})
|
||||
logger.debug('Deleted inventory {} as user {}.'.format(inventory_id, user_id))
|
||||
except DatabaseError:
|
||||
logger.exception('Database error deleting inventory {}, but will retry.'.format(inventory_id))
|
||||
if retries > 0:
|
||||
|
||||
@@ -145,3 +145,122 @@ def test_delete_ad_hoc_command_in_active_state(ad_hoc_command_factory, delete, a
|
||||
adhoc = ad_hoc_command_factory(initial_state=status)
|
||||
url = reverse('api:ad_hoc_command_detail', kwargs={'pk': adhoc.pk})
|
||||
delete(url, None, admin, expect=403)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def job_with_heavy_fields(job_factory):
|
||||
job = job_factory()
|
||||
job.extra_vars = '{"some_var": "some_value"}'
|
||||
job.artifacts = {"some_artifact": "some_value"}
|
||||
job.save()
|
||||
return job
|
||||
|
||||
|
||||
def _job_result(response, job_id):
|
||||
for row in response.data['results']:
|
||||
if row['id'] == job_id:
|
||||
return row
|
||||
raise AssertionError('job {} not found in {}'.format(job_id, [r['id'] for r in response.data['results']]))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_includes_heavy_fields_by_default(get, admin, job_with_heavy_fields):
|
||||
response = get(reverse('api:unified_job_list') + '?id={}'.format(job_with_heavy_fields.id), admin, expect=200)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'artifacts' in row
|
||||
assert 'extra_vars' in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_exclude_artifacts(get, admin, job_with_heavy_fields):
|
||||
response = get(
|
||||
reverse('api:unified_job_list') + '?id={}&exclude=artifacts'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'artifacts' not in row
|
||||
assert 'extra_vars' in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_exclude_extra_vars(get, admin, job_with_heavy_fields):
|
||||
response = get(
|
||||
reverse('api:unified_job_list') + '?id={}&exclude=extra_vars'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'extra_vars' not in row
|
||||
assert 'artifacts' in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_exclude_both(get, admin, job_with_heavy_fields):
|
||||
response = get(
|
||||
reverse('api:unified_job_list') + '?id={}&exclude=artifacts,extra_vars'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'artifacts' not in row
|
||||
assert 'extra_vars' not in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_exclude_tolerates_whitespace(get, admin, job_with_heavy_fields):
|
||||
response = get(
|
||||
reverse('api:unified_job_list') + '?id={}&exclude=%20artifacts%20,%20extra_vars%20'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'artifacts' not in row
|
||||
assert 'extra_vars' not in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_exclude_ignores_unknown(get, admin, job_with_heavy_fields):
|
||||
response = get(
|
||||
reverse('api:unified_job_list') + '?id={}&exclude=does_not_exist'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'artifacts' in row
|
||||
assert 'extra_vars' in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unified_jobs_list_exclude_does_not_honor_always_stripped(get, admin, job_with_heavy_fields):
|
||||
# Always-stripped fields like event_processing_finished, job_args, result_traceback
|
||||
# must remain stripped regardless of the ?exclude= param — they cannot be re-included.
|
||||
response = get(
|
||||
reverse('api:unified_job_list') + '?id={}'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'event_processing_finished' not in row
|
||||
assert 'job_args' not in row
|
||||
assert 'result_traceback' not in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_jobs_list_includes_heavy_fields_by_default(get, admin, job_with_heavy_fields):
|
||||
response = get(reverse('api:job_list') + '?id={}'.format(job_with_heavy_fields.id), admin, expect=200)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'artifacts' in row
|
||||
assert 'extra_vars' in row
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_jobs_list_exclude_extra_vars(get, admin, job_with_heavy_fields):
|
||||
response = get(
|
||||
reverse('api:job_list') + '?id={}&exclude=extra_vars'.format(job_with_heavy_fields.id),
|
||||
admin,
|
||||
expect=200,
|
||||
)
|
||||
row = _job_result(response, job_with_heavy_fields.id)
|
||||
assert 'extra_vars' not in row
|
||||
assert 'artifacts' in row
|
||||
|
||||
@@ -830,14 +830,13 @@ class MockCopy:
|
||||
events = []
|
||||
index = -1
|
||||
|
||||
def __init__(self, sql):
|
||||
def __init__(self):
|
||||
self.events = []
|
||||
parts = sql.split(' ')
|
||||
tablename = parts[parts.index('from') + 1]
|
||||
for cls in (JobEvent, AdHocCommandEvent, ProjectUpdateEvent, InventoryUpdateEvent, SystemJobEvent):
|
||||
if cls._meta.db_table == tablename:
|
||||
for event in cls.objects.order_by('start_line').all():
|
||||
self.events.append(event.stdout)
|
||||
events = list(cls.objects.order_by('start_line').values_list('stdout', flat=True))
|
||||
if events:
|
||||
self.events = events
|
||||
break
|
||||
|
||||
def read(self):
|
||||
self.index = self.index + 1
|
||||
@@ -858,9 +857,8 @@ def sqlite_copy(request, mocker):
|
||||
# copy is postgres-specific, and SQLite doesn't support it; mock its
|
||||
# behavior to test that it writes a file that contains stdout from events
|
||||
|
||||
def write_stdout(self, sql):
|
||||
mock_copy = MockCopy(sql)
|
||||
return mock_copy
|
||||
def write_stdout(self, sql, params=None):
|
||||
return MockCopy()
|
||||
|
||||
mocker.patch.object(SQLiteCursorWrapper, 'copy', write_stdout, create=True)
|
||||
|
||||
|
||||
@@ -8,9 +8,16 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from awx.main.tasks.system import CleanupImagesAndFiles, execution_node_health_check, inspect_established_receptor_connections, clear_setting_cache
|
||||
from awx.main.tasks.system import (
|
||||
CleanupImagesAndFiles,
|
||||
execution_node_health_check,
|
||||
inspect_established_receptor_connections,
|
||||
clear_setting_cache,
|
||||
_batched_delete_inventory,
|
||||
)
|
||||
from awx.main.management.commands.dispatcherd import Command
|
||||
from awx.main.models import Instance, Job, ReceptorAddress, InstanceLink
|
||||
from awx.main.models import Instance, Inventory, Job, Organization, ReceptorAddress, InstanceLink
|
||||
from awx.main.models.inventory import Group, Host
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -105,6 +112,42 @@ def test_folder_cleanup_multiple_running_jobs(job_folder_factory, me_inst):
|
||||
assert [os.path.exists(d) for d in dirs] == [True for i in range(num_jobs)]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestBatchedDeleteInventory:
|
||||
def _make_inventory_with_hosts(self, count):
|
||||
from django.utils import timezone
|
||||
|
||||
now = timezone.now()
|
||||
org = Organization.objects.create(name='test-org')
|
||||
inv = Inventory.objects.create(name='test-inv', organization=org)
|
||||
group = Group.objects.create(name='test-group', inventory=inv)
|
||||
hosts = [Host(name=f'host-{i}', inventory=inv, created=now, modified=now) for i in range(count)]
|
||||
Host.objects.bulk_create(hosts)
|
||||
group.hosts.set(Host.objects.filter(inventory=inv))
|
||||
return inv
|
||||
|
||||
def test_deletes_all_hosts_and_inventory(self):
|
||||
inv = self._make_inventory_with_hosts(10)
|
||||
inv_id = inv.id
|
||||
_batched_delete_inventory(inv, batch_size=3)
|
||||
assert not Host.objects.filter(inventory_id=inv_id).exists()
|
||||
assert not Group.objects.filter(inventory_id=inv_id).exists()
|
||||
assert not Inventory.objects.filter(id=inv_id).exists()
|
||||
|
||||
def test_no_hosts(self):
|
||||
inv = self._make_inventory_with_hosts(0)
|
||||
inv_id = inv.id
|
||||
_batched_delete_inventory(inv)
|
||||
assert not Inventory.objects.filter(id=inv_id).exists()
|
||||
|
||||
def test_exactly_one_batch(self):
|
||||
inv = self._make_inventory_with_hosts(5)
|
||||
inv_id = inv.id
|
||||
_batched_delete_inventory(inv, batch_size=5)
|
||||
assert not Host.objects.filter(inventory_id=inv_id).exists()
|
||||
assert not Inventory.objects.filter(id=inv_id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_clear_setting_cache_log_level_branch(settings):
|
||||
settings.LOG_AGGREGATOR_LEVEL = 'DEBUG'
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_unified_job_detail_exclusive_fields():
|
||||
For each type, assert that the only fields allowed to be exclusive to
|
||||
detail view are the allowed types
|
||||
"""
|
||||
allowed_detail_fields = frozenset(('result_traceback', 'job_args', 'job_cwd', 'job_env', 'event_processing_finished', 'artifacts'))
|
||||
allowed_detail_fields = frozenset(('result_traceback', 'job_args', 'job_cwd', 'job_env', 'event_processing_finished'))
|
||||
for cls in UnifiedJob.__subclasses__():
|
||||
list_serializer = getattr(serializers, '{}ListSerializer'.format(cls.__name__))
|
||||
detail_serializer = getattr(serializers, '{}Serializer'.format(cls.__name__))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import collections
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
@@ -125,7 +124,7 @@ def test_sql_above_threshold(tmpdir):
|
||||
args, kw = _call
|
||||
assert args == ('EXPLAIN VERBOSE {}'.format(QUERY['sql']),)
|
||||
|
||||
path = os.path.join(tmpdir, '{}.sqlite'.format(os.path.basename(sys.argv[0])))
|
||||
path = os.path.join(tmpdir, 'unknown.sqlite')
|
||||
assert os.path.exists(path)
|
||||
|
||||
# verify the results
|
||||
|
||||
@@ -438,7 +438,7 @@ class ControllerAPIModule(ControllerModule):
|
||||
raise RuntimeError('Expected list from API at {0}, got: {1}'.format(endpoint, response))
|
||||
next_page = response['json']['next']
|
||||
|
||||
if response['json']['count'] > 10000:
|
||||
if response['json'].get('count', 0) > 10000:
|
||||
self.fail_json(msg='The number of items being queried for is higher than 10,000.')
|
||||
|
||||
while next_page is not None:
|
||||
@@ -493,8 +493,11 @@ class ControllerAPIModule(ControllerModule):
|
||||
fail_msg += ', detail: {0}'.format(response['json']['detail'])
|
||||
self.fail_json(msg=fail_msg)
|
||||
|
||||
if 'count' not in response['json'] or 'results' not in response['json']:
|
||||
self.fail_json(msg="The endpoint did not provide count and results")
|
||||
if 'results' not in response['json']:
|
||||
self.fail_json(msg="The endpoint did not provide a results list")
|
||||
|
||||
if 'count' not in response['json']:
|
||||
response['json']['count'] = len(response['json']['results'])
|
||||
|
||||
if response['json']['count'] == 0:
|
||||
if allow_none:
|
||||
|
||||
@@ -93,7 +93,15 @@ def main():
|
||||
metadata = module.params.get('metadata')
|
||||
state = module.params.get('state')
|
||||
|
||||
target_credential_id = module.resolve_name_to_id('credentials', target_credential)
|
||||
# The target credential lookup should not fail if the target credential is absent and the
|
||||
# state on the credential input source is also absent. If the credential input source has a
|
||||
# state of present, then this should fail as the target credential cannot be nonexistent.
|
||||
target_credential_lookup = module.get_one('credentials', name_or_id=target_credential, allow_none=(state == 'absent'))
|
||||
|
||||
if target_credential_lookup is None:
|
||||
module.exit_json(**{'changed': False})
|
||||
else:
|
||||
target_credential_id = target_credential_lookup['id']
|
||||
|
||||
# Attempt to look up the object based on the target credential and input field
|
||||
lookup_data = {
|
||||
|
||||
@@ -62,13 +62,18 @@ subscriptions:
|
||||
EXAMPLES = '''
|
||||
- name: Get subscriptions
|
||||
subscriptions:
|
||||
client_id: "c6bd7594-d776-46e5-8156-6d17af147479"
|
||||
client_secret: "MO9QUvoOZ5fc5JQKXoTch1AsTLI7nFsZ"
|
||||
client_id: "00000000-0000-0000-0000-000000000000"
|
||||
client_secret: "your-client-secret-here"
|
||||
|
||||
- name: Get subscriptions with username and password
|
||||
subscriptions:
|
||||
username: "my_username"
|
||||
password: "my_password"
|
||||
|
||||
- name: Get subscriptions with a filter
|
||||
subscriptions:
|
||||
client_id: "c6bd7594-d776-46e5-8156-6d17af147479"
|
||||
client_secret: "MO9QUvoOZ5fc5JQKXoTch1AsTLI7nFsZ"
|
||||
client_id: "00000000-0000-0000-0000-000000000000"
|
||||
client_secret: "your-client-secret-here"
|
||||
filters:
|
||||
product_name: "Red Hat Ansible Automation Platform"
|
||||
support_level: "Self-Support"
|
||||
|
||||
@@ -358,3 +358,95 @@ def test_centrify_vault_credential_source(run_module, admin_user, organization,
|
||||
assert cis.target_credential.name == tgt_cred.name
|
||||
assert cis.input_field_name == 'password'
|
||||
assert result['id'] == cis.pk
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_credential_input_source_delete(run_module, admin_user, organization, source_cred_aim, silence_deprecation):
|
||||
ct = CredentialType.defaults['ssh']()
|
||||
ct.save()
|
||||
tgt_cred = Credential.objects.create(name='Test Machine Credential', organization=organization, credential_type=ct, inputs={'username': 'nick'})
|
||||
|
||||
result = run_module(
|
||||
'credential_input_source',
|
||||
dict(
|
||||
source_credential=source_cred_aim.name,
|
||||
target_credential=tgt_cred.name,
|
||||
input_field_name='password',
|
||||
metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"},
|
||||
state='present',
|
||||
),
|
||||
admin_user,
|
||||
)
|
||||
|
||||
assert not result.get('failed', False), result.get('msg', result)
|
||||
assert result.get('changed'), result
|
||||
assert CredentialInputSource.objects.count() == 1
|
||||
|
||||
delete_result = run_module(
|
||||
'credential_input_source',
|
||||
dict(
|
||||
target_credential=tgt_cred.name,
|
||||
input_field_name='password',
|
||||
state='absent',
|
||||
),
|
||||
admin_user,
|
||||
)
|
||||
|
||||
assert not delete_result.get('failed', False), delete_result.get('msg', delete_result)
|
||||
assert delete_result.get('changed'), delete_result
|
||||
assert CredentialInputSource.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_credential_input_source_delete_nonexistent(run_module, admin_user, organization, source_cred_aim, silence_deprecation):
|
||||
ct = CredentialType.defaults['ssh']()
|
||||
ct.save()
|
||||
tgt_cred = Credential.objects.create(name='Test Machine Credential', organization=organization, credential_type=ct, inputs={'username': 'bob'})
|
||||
|
||||
result = run_module(
|
||||
'credential_input_source',
|
||||
dict(
|
||||
target_credential=tgt_cred.name,
|
||||
input_field_name='password',
|
||||
state='absent',
|
||||
),
|
||||
admin_user,
|
||||
)
|
||||
|
||||
assert not result.get('failed', False), result.get('msg', result)
|
||||
assert not result.get('changed'), result
|
||||
assert CredentialInputSource.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_credential_input_source_delete_missing_target_credential(run_module, admin_user, organization, silence_deprecation):
|
||||
result = run_module(
|
||||
'credential_input_source',
|
||||
dict(
|
||||
target_credential='nonexistent-credential',
|
||||
input_field_name='password',
|
||||
state='absent',
|
||||
),
|
||||
admin_user,
|
||||
)
|
||||
|
||||
assert not result.get('failed', False), result.get('msg', result)
|
||||
assert not result.get('changed'), result
|
||||
assert CredentialInputSource.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_credential_input_source_create_missing_target_credential(run_module, admin_user, organization, source_cred_aim, silence_deprecation):
|
||||
result = run_module(
|
||||
'credential_input_source',
|
||||
dict(
|
||||
source_credential=source_cred_aim.name,
|
||||
target_credential='nonexistent-credential',
|
||||
input_field_name='password',
|
||||
state='present',
|
||||
),
|
||||
admin_user,
|
||||
)
|
||||
|
||||
assert result.get('failed', True), "Should fail when target credential doesn't exist with state: present"
|
||||
assert CredentialInputSource.objects.count() == 0
|
||||
|
||||
277
licenses/backports-zstd.txt
Normal file
277
licenses/backports-zstd.txt
Normal file
@@ -0,0 +1,277 @@
|
||||
A. HISTORY OF THE SOFTWARE
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations, which became
|
||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||
https://www.python.org/psf/) was formed, a non-profit organization
|
||||
created specifically to own Python-related Intellectual Property.
|
||||
Zope Corporation was a sponsoring member of the PSF.
|
||||
|
||||
All Python releases are Open Source (see https://opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2 and above 2.1.1 2001-now PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
Python software and documentation are licensed under the
|
||||
Python Software Foundation License Version 2.
|
||||
|
||||
Starting with Python 3.8.6, examples, recipes, and other code in
|
||||
the documentation are dual licensed under the PSF License Version 2
|
||||
and the Zero-Clause BSD license.
|
||||
|
||||
Some software incorporated into Python is under different licenses.
|
||||
The licenses are listed with code falling under that license.
|
||||
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved"
|
||||
are retained in Python alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2016, Gregory Szorc
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,4 +1,4 @@
|
||||
aiohttp>=3.12.14 # CVE-2024-30251
|
||||
aiohttp>=3.13.5 # CVE-2024-30251, kubernetes>=36.0.2 requires >=3.13.5
|
||||
ansi2html # Used to format the stdout from jobs into html for display
|
||||
jq # used for indirect host counting feature
|
||||
asn1
|
||||
@@ -36,7 +36,7 @@ maturin # pydantic-core build dep
|
||||
msgpack
|
||||
msrestazure
|
||||
OPA-python-client==2.0.2 # upgrading requires urllib3 2.5.0+ which is blocked by other deps
|
||||
kubernetes>=36.0.0 # fixes NO_PROXY silently being reset to None
|
||||
kubernetes>=36.0.2 # fixes NO_PROXY silently being reset to None
|
||||
openshift
|
||||
opentelemetry-api~=1.37 # new y streams can be drastically different, in a good way
|
||||
opentelemetry-sdk~=1.37
|
||||
|
||||
@@ -6,7 +6,7 @@ aiofiles==24.1.0
|
||||
# via opa-python-client
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via aiohttp
|
||||
aiohttp[speedups]==3.13.0
|
||||
aiohttp[speedups]==3.14.1
|
||||
# via
|
||||
# -r /awx_devel/requirements/requirements.in
|
||||
# aiohttp-retry
|
||||
@@ -67,6 +67,8 @@ azure-keyvault-keys==4.11.0
|
||||
# via azure-keyvault
|
||||
azure-keyvault-secrets==4.10.0
|
||||
# via azure-keyvault
|
||||
backports-zstd==1.6.0
|
||||
# via aiohttp
|
||||
boto3==1.40.46
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
botocore==1.40.46
|
||||
@@ -74,7 +76,7 @@ botocore==1.40.46
|
||||
# -r /awx_devel/requirements/requirements.in
|
||||
# boto3
|
||||
# s3transfer
|
||||
brotli==1.1.0
|
||||
brotli==1.2.0
|
||||
# via aiohttp
|
||||
cachetools==6.2.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
@@ -252,7 +254,7 @@ jsonschema==4.25.1
|
||||
# drf-spectacular
|
||||
jsonschema-specifications==2025.9.1
|
||||
# via jsonschema
|
||||
kubernetes==36.0.0
|
||||
kubernetes==36.0.2
|
||||
# via
|
||||
# -r /awx_devel/requirements/requirements.in
|
||||
# openshift
|
||||
@@ -500,6 +502,7 @@ txaio==25.9.2
|
||||
# via autobahn
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
# azure-core
|
||||
# azure-identity
|
||||
@@ -544,8 +547,6 @@ zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
zope-interface==8.0.1
|
||||
# via twisted
|
||||
zstandard==0.25.0
|
||||
# via aiohttp
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==25.3
|
||||
|
||||
Reference in New Issue
Block a user