mirror of
https://github.com/ansible/awx.git
synced 2026-01-08 14:32:07 -03:30
Upgrade to Django 5.2 LTS (#16185)
Upgrade to Django 5.2 LTS with compatibility fixes across fields, migrations, dispatch config, tests, and dev deps. Dependencies: - Upgrade django to 5.2.8 and relax requirements.in to >=5.2,<5.3. - Bump django-debug-toolbar to >=6.0 for compatibility. Backend: - awx/conf/fields.py: switch URL TLD regex to use DomainNameValidator.ul in custom URLField. - awx/main/management/commands/gather_analytics.py: use datetime.timezone.utc for naïve datetime handling. - awx/main/dispatch/config.py: add mock_publish option; avoid DB access for test runs, set default max_workers, and support a noop broker. Migrations (SQLite/Postgres compatibility): - Add awx/main/migrations/_sqlite_helper.py with db-aware AlterIndexTogether/RenameIndex wrappers; consume in 0144_event_partitions.py and 0184_django_indexes.py. - Update 0187_hop_nodes.py to use CheckConstraint(condition=...). - Add 0205_alter_instance_peers_alter_job_hosts_and_more.py adjusting through_fields/relations on instance.peers, job.hosts, and role.ancestors. - _dab_rbac.py: iterate roles with chunk_size=1000 for migration performance. Tests: Include hcp_terraform in default credential types in test_credential.py. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Alan Rominger <arominge@redhat.com>
This commit is contained in:
parent
711b018ae7
commit
b24156805a
@ -6,7 +6,7 @@ import urllib.parse as urlparse
|
||||
from collections import OrderedDict
|
||||
|
||||
# Django
|
||||
from django.core.validators import URLValidator, _lazy_re_compile
|
||||
from django.core.validators import URLValidator, DomainNameValidator, _lazy_re_compile
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Django REST Framework
|
||||
@ -160,10 +160,11 @@ class StringListIsolatedPathField(StringListField):
|
||||
class URLField(CharField):
|
||||
# these lines set up a custom regex that allow numbers in the
|
||||
# top-level domain
|
||||
|
||||
tld_re = (
|
||||
r'\.' # dot
|
||||
r'(?!-)' # can't start with a dash
|
||||
r'(?:[a-z' + URLValidator.ul + r'0-9' + '-]{2,63}' # domain label, this line was changed from the original URLValidator
|
||||
r'(?:[a-z' + DomainNameValidator.ul + r'0-9' + '-]{2,63}' # domain label, this line was changed from the original URLValidator
|
||||
r'|xn--[a-z0-9]{1,59})' # or punycode label
|
||||
r'(?<!-)' # can't end with a dash
|
||||
r'\.?' # may have a trailing dot
|
||||
|
||||
@ -11,13 +11,22 @@ def get_dispatcherd_config(for_service: bool = False, mock_publish: bool = False
|
||||
Parameters:
|
||||
for_service: if True, include dynamic options needed for running the dispatcher service
|
||||
this will require database access, you should delay evaluation until after app setup
|
||||
mock_publish: if True, use mock values that don't require database access
|
||||
this is used during tests to avoid database queries during app initialization
|
||||
"""
|
||||
# When mock_publish=True (e.g., during tests), use a default value to avoid
|
||||
# database access in get_auto_max_workers() which queries settings.IS_K8S
|
||||
if mock_publish:
|
||||
max_workers = 20 # Reasonable default for tests
|
||||
else:
|
||||
max_workers = get_auto_max_workers()
|
||||
|
||||
config = {
|
||||
"version": 2,
|
||||
"service": {
|
||||
"pool_kwargs": {
|
||||
"min_workers": settings.JOB_EVENT_WORKERS,
|
||||
"max_workers": get_auto_max_workers(),
|
||||
"max_workers": max_workers,
|
||||
},
|
||||
"main_kwargs": {"node_id": settings.CLUSTER_HOST_ID},
|
||||
"process_manager_cls": "ForkServerManager",
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from awx.main import analytics
|
||||
from dateutil import parser
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@ -38,10 +38,10 @@ class Command(BaseCommand):
|
||||
|
||||
since = parser.parse(opt_since) if opt_since else None
|
||||
if since and since.tzinfo is None:
|
||||
since = since.replace(tzinfo=timezone.utc)
|
||||
since = since.replace(tzinfo=datetime.timezone.utc)
|
||||
until = parser.parse(opt_until) if opt_until else None
|
||||
if until and until.tzinfo is None:
|
||||
until = until.replace(tzinfo=timezone.utc)
|
||||
until = until.replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
if opt_ship and opt_dry_run:
|
||||
self.logger.error('Both --ship and --dry-run cannot be processed at the same time.')
|
||||
|
||||
@ -237,7 +237,7 @@ class Migration(migrations.Migration):
|
||||
db_index=False, editable=False, on_delete=models.deletion.DO_NOTHING, related_name='system_job_events', to='main.SystemJob'
|
||||
),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
dbawaremigrations.AlterIndexTogether(
|
||||
name='adhoccommandevent',
|
||||
index_together={
|
||||
('ad_hoc_command', 'job_created', 'event'),
|
||||
@ -245,11 +245,11 @@ class Migration(migrations.Migration):
|
||||
('ad_hoc_command', 'job_created', 'uuid'),
|
||||
},
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
dbawaremigrations.AlterIndexTogether(
|
||||
name='inventoryupdateevent',
|
||||
index_together={('inventory_update', 'job_created', 'counter'), ('inventory_update', 'job_created', 'uuid')},
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
dbawaremigrations.AlterIndexTogether(
|
||||
name='jobevent',
|
||||
index_together={
|
||||
('job', 'job_created', 'counter'),
|
||||
@ -258,7 +258,7 @@ class Migration(migrations.Migration):
|
||||
('job', 'job_created', 'parent_uuid'),
|
||||
},
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
dbawaremigrations.AlterIndexTogether(
|
||||
name='projectupdateevent',
|
||||
index_together={
|
||||
('project_update', 'job_created', 'uuid'),
|
||||
@ -266,7 +266,7 @@ class Migration(migrations.Migration):
|
||||
('project_update', 'job_created', 'counter'),
|
||||
},
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
dbawaremigrations.AlterIndexTogether(
|
||||
name='systemjobevent',
|
||||
index_together={('system_job', 'job_created', 'uuid'), ('system_job', 'job_created', 'counter')},
|
||||
),
|
||||
|
||||
@ -6,6 +6,8 @@ from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
from ._sqlite_helper import dbawaremigrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
@ -15,92 +17,92 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='adhoccommandevent',
|
||||
new_name='main_adhocc_ad_hoc__1e4d24_idx',
|
||||
old_fields=('ad_hoc_command', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='adhoccommandevent',
|
||||
new_name='main_adhocc_ad_hoc__e72142_idx',
|
||||
old_fields=('ad_hoc_command', 'job_created', 'event'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='adhoccommandevent',
|
||||
new_name='main_adhocc_ad_hoc__a57777_idx',
|
||||
old_fields=('ad_hoc_command', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='inventoryupdateevent',
|
||||
new_name='main_invent_invento_f72b21_idx',
|
||||
old_fields=('inventory_update', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='inventoryupdateevent',
|
||||
new_name='main_invent_invento_364dcb_idx',
|
||||
old_fields=('inventory_update', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_40a56d_idx',
|
||||
old_fields=('job', 'job_created', 'parent_uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_3c4a4a_idx',
|
||||
old_fields=('job', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_51c382_idx',
|
||||
old_fields=('job', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_0ddc6b_idx',
|
||||
old_fields=('job', 'job_created', 'event'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='projectupdateevent',
|
||||
new_name='main_projec_project_449bbd_idx',
|
||||
old_fields=('project_update', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='projectupdateevent',
|
||||
new_name='main_projec_project_69559a_idx',
|
||||
old_fields=('project_update', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='projectupdateevent',
|
||||
new_name='main_projec_project_c44b7c_idx',
|
||||
old_fields=('project_update', 'job_created', 'event'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='role',
|
||||
new_name='main_rbac_r_content_979bdd_idx',
|
||||
old_fields=('content_type', 'object_id'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='roleancestorentry',
|
||||
new_name='main_rbac_r_ancesto_b44606_idx',
|
||||
old_fields=('ancestor', 'content_type_id', 'role_field'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='roleancestorentry',
|
||||
new_name='main_rbac_r_ancesto_22b9f0_idx',
|
||||
old_fields=('ancestor', 'content_type_id', 'object_id'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='roleancestorentry',
|
||||
new_name='main_rbac_r_ancesto_c87b87_idx',
|
||||
old_fields=('ancestor', 'descendent'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='systemjobevent',
|
||||
new_name='main_system_system__e39825_idx',
|
||||
old_fields=('system_job', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
dbawaremigrations.RenameIndex(
|
||||
model_name='systemjobevent',
|
||||
new_name='main_system_system__73537a_idx',
|
||||
old_fields=('system_job', 'job_created', 'counter'),
|
||||
|
||||
@ -69,7 +69,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='instancelink',
|
||||
constraint=models.CheckConstraint(check=models.Q(('source', models.F('target')), _negated=True), name='source_and_target_can_not_be_equal'),
|
||||
constraint=models.CheckConstraint(condition=models.Q(('source', models.F('target')), _negated=True), name='source_and_target_can_not_be_equal'),
|
||||
),
|
||||
migrations.RunPython(automatically_peer_from_control_plane),
|
||||
]
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 18:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0204_squashed_deletions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='peers',
|
||||
field=models.ManyToManyField(
|
||||
related_name='peers_from', through='main.InstanceLink', through_fields=('source', 'target'), to='main.receptoraddress'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='hosts',
|
||||
field=models.ManyToManyField(editable=False, related_name='jobs', through='main.JobHostSummary', through_fields=('job', 'host'), to='main.host'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='ancestors',
|
||||
field=models.ManyToManyField(
|
||||
related_name='descendents', through='main.RoleAncestorEntry', through_fields=('descendent', 'ancestor'), to='main.role'
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -194,7 +194,7 @@ def migrate_to_new_rbac(apps, schema_editor):
|
||||
# NOTE: this import is expected to break at some point, and then just move the data here
|
||||
from awx.main.models.rbac import role_descriptions
|
||||
|
||||
for role in Role.objects.prefetch_related('members', 'parents').iterator():
|
||||
for role in Role.objects.prefetch_related('members', 'parents').iterator(chunk_size=1000):
|
||||
if role.singleton_name:
|
||||
continue # only bothering to migrate object roles
|
||||
|
||||
|
||||
@ -1,6 +1,78 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class AlterIndexTogether(migrations.AlterIndexTogether):
|
||||
"""
|
||||
Database-aware AlterIndexTogether that handles SQLite's missing indexes gracefully.
|
||||
|
||||
In Django 5.2+, SQLite table rewrites (triggered by AlterField operations)
|
||||
can drop multi-column indexes. For SQLite, this catches the ValueError and
|
||||
ignores it when the index doesn't exist. For PostgreSQL, uses standard behavior.
|
||||
"""
|
||||
|
||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||
if not schema_editor.connection.vendor.startswith('postgres'):
|
||||
# SQLite-specific handling: ignore missing indexes from table rewrites
|
||||
try:
|
||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||
except ValueError as exc:
|
||||
if "Found wrong number (0) of constraints" in str(exc) or "Found wrong number (0) of indexes" in str(exc):
|
||||
return
|
||||
raise
|
||||
else:
|
||||
# PostgreSQL: standard behavior
|
||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||
|
||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||
if not schema_editor.connection.vendor.startswith('postgres'):
|
||||
# SQLite-specific handling: ignore missing indexes from table rewrites
|
||||
try:
|
||||
super().database_backwards(app_label, schema_editor, from_state, to_state)
|
||||
except ValueError as exc:
|
||||
if "Found wrong number (0) of constraints" in str(exc) or "Found wrong number (0) of indexes" in str(exc):
|
||||
return
|
||||
raise
|
||||
else:
|
||||
# PostgreSQL: standard behavior
|
||||
super().database_backwards(app_label, schema_editor, from_state, to_state)
|
||||
|
||||
|
||||
class RenameIndex(migrations.RenameIndex):
|
||||
"""
|
||||
Database-aware RenameIndex that handles SQLite's missing indexes gracefully.
|
||||
|
||||
In Django 5.2+, SQLite table rewrites (triggered by AlterField operations)
|
||||
can drop multi-column indexes. For SQLite, this catches the ValueError and
|
||||
ignores it when the index doesn't exist. For PostgreSQL, uses standard behavior.
|
||||
"""
|
||||
|
||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||
if not schema_editor.connection.vendor.startswith('postgres'):
|
||||
# SQLite-specific handling: ignore missing indexes from table rewrites
|
||||
try:
|
||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||
except ValueError as exc:
|
||||
if "Found wrong number (0) of constraints" in str(exc) or "wrong number (0) of indexes" in str(exc):
|
||||
return
|
||||
raise
|
||||
else:
|
||||
# PostgreSQL: standard behavior
|
||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||
|
||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||
if not schema_editor.connection.vendor.startswith('postgres'):
|
||||
# SQLite-specific handling: ignore missing indexes from table rewrites
|
||||
try:
|
||||
super().database_backwards(app_label, schema_editor, from_state, to_state)
|
||||
except ValueError as exc:
|
||||
if "Found wrong number (0) of constraints" in str(exc) or "wrong number (0) of indexes" in str(exc):
|
||||
return
|
||||
raise
|
||||
else:
|
||||
# PostgreSQL: standard behavior
|
||||
super().database_backwards(app_label, schema_editor, from_state, to_state)
|
||||
|
||||
|
||||
class RunSQL(migrations.operations.special.RunSQL):
|
||||
"""
|
||||
Bit of a hack here. Django actually wants this decision made in the router
|
||||
@ -56,6 +128,8 @@ class RunPython(migrations.operations.special.RunPython):
|
||||
class _sqlitemigrations:
|
||||
RunPython = RunPython
|
||||
RunSQL = RunSQL
|
||||
AlterIndexTogether = AlterIndexTogether
|
||||
RenameIndex = RenameIndex
|
||||
|
||||
|
||||
dbawaremigrations = _sqlitemigrations()
|
||||
|
||||
@ -13,7 +13,7 @@ cryptography
|
||||
Cython
|
||||
daphne
|
||||
distro
|
||||
django==4.2.26 # CVE-2025-32873
|
||||
django>=5.2,<5.3 # Django 5.2 LTS, allow patch updates
|
||||
django-cors-headers
|
||||
django-crum
|
||||
django-extensions
|
||||
|
||||
@ -124,7 +124,7 @@ dispatcherd==2025.5.21
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
distro==1.9.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
django==4.2.26
|
||||
django==5.2.8
|
||||
# via
|
||||
# -r /awx_devel/requirements/requirements.in
|
||||
# channels
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
build
|
||||
django-debug-toolbar==3.2.4
|
||||
django-debug-toolbar>=6.0 # Django 5.2 compatibility
|
||||
django-test-migrations
|
||||
drf-spectacular>=0.27.0 # Modern OpenAPI 3.0 schema generator
|
||||
# pprofile - re-add once https://github.com/vpelletier/pprofile/issues/41 is addressed
|
||||
@ -28,4 +28,3 @@ pip>=21.3,<=24.0 # PEP 660 – Editable installs for pyproject.toml based builds
|
||||
debugpy
|
||||
remote-pdb
|
||||
sdb
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user