mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 05:15:02 -02:30
allow naive UNTILs to be specified for schedule rrules
This commit is contained in:
committed by
Jared Tabor
parent
fbe2391b86
commit
441e5cc9c2
@@ -1,8 +1,10 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
import logging
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
import dateutil.rrule
|
import dateutil.rrule
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
@@ -11,7 +13,7 @@ from dateutil.tz import datetime_exists, tzutc
|
|||||||
# Django
|
# Django
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now, make_aware
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
@@ -129,6 +131,56 @@ class Schedule(CommonModel, LaunchTimeConfig):
|
|||||||
Apply our own custom rrule parsing requirements
|
Apply our own custom rrule parsing requirements
|
||||||
"""
|
"""
|
||||||
kwargs['forceset'] = True
|
kwargs['forceset'] = True
|
||||||
|
|
||||||
|
#
|
||||||
|
# RFC5545 specifies that the UNTIL rule part MUST ALWAYS be a date
|
||||||
|
# with UTC time. This is extra work for API implementers because
|
||||||
|
# it requires them to perform DTSTART local -> UTC datetime coercion on
|
||||||
|
# POST and UTC -> DTSTART local coercion on GET.
|
||||||
|
#
|
||||||
|
# This block of code is a departure from the RFC. If you send an
|
||||||
|
# rrule like this to the API (without a Z on the UNTIL):
|
||||||
|
#
|
||||||
|
# DTSTART;TZID=America/New_York:20180502T150000 RRULE:FREQ=HOURLY;INTERVAL=1;UNTIL=20180502T180000
|
||||||
|
#
|
||||||
|
# ...we'll assume that the naive UNTIL is intended to match the DTSTART
|
||||||
|
# timezone (America/New_York), and so we'll coerce to UTC _for you_
|
||||||
|
# automatically.
|
||||||
|
#
|
||||||
|
if 'until=' in rrule.lower():
|
||||||
|
# if DTSTART;TZID= is used, coerce "naive" UNTIL values
|
||||||
|
# to the proper UTC date
|
||||||
|
match_until = re.match(".*?UNTIL\=(?P<until>[0-9]+T[0-9]+)(?P<utcflag>Z?)", rrule)
|
||||||
|
if not len(match_until.group('utcflag')):
|
||||||
|
# rrule = DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T170000
|
||||||
|
|
||||||
|
# Find the UNTIL=N part of the string
|
||||||
|
# naive_until = 20200601T170000
|
||||||
|
naive_until = match_until.group('until')
|
||||||
|
|
||||||
|
# What is the DTSTART timezone for:
|
||||||
|
# DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T170000Z
|
||||||
|
# local_tz = tzfile('/usr/share/zoneinfo/America/New_York')
|
||||||
|
local_tz = dateutil.rrule.rrulestr(
|
||||||
|
rrule.replace(naive_until, naive_until + 'Z'),
|
||||||
|
tzinfos={x: tzutc() for x in dateutil.parser.parserinfo().UTCZONE}
|
||||||
|
)._dtstart.tzinfo
|
||||||
|
|
||||||
|
# Make a datetime object with tzinfo=<the DTSTART timezone>
|
||||||
|
# localized_until = datetime.datetime(2020, 6, 1, 17, 0, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York'))
|
||||||
|
localized_until = make_aware(
|
||||||
|
datetime.datetime.strptime(naive_until, "%Y%m%dT%H%M%S"),
|
||||||
|
local_tz
|
||||||
|
)
|
||||||
|
|
||||||
|
# Coerce the datetime to UTC and format it as a string w/ Zulu format
|
||||||
|
# utc_until = 20200601T220000Z
|
||||||
|
utc_until = localized_until.astimezone(pytz.utc).strftime('%Y%m%dT%H%M%SZ')
|
||||||
|
|
||||||
|
# rrule was: DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T170000
|
||||||
|
# rrule is now: DTSTART;TZID=America/New_York:20200601T120000 RRULE:...;UNTIL=20200601T220000Z
|
||||||
|
rrule = rrule.replace(naive_until, utc_until)
|
||||||
|
|
||||||
x = dateutil.rrule.rrulestr(rrule, **kwargs)
|
x = dateutil.rrule.rrulestr(rrule, **kwargs)
|
||||||
|
|
||||||
for r in x._rrule:
|
for r in x._rrule:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.utils.timezone import now
|
||||||
import mock
|
import mock
|
||||||
import pytest
|
import pytest
|
||||||
import pytz
|
import pytz
|
||||||
@@ -131,31 +132,19 @@ def test_utc_until(job_template, until, dtend):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize('dtstart, until', [
|
@pytest.mark.parametrize('dtstart, until', [
|
||||||
['20180601T120000Z', '20180602T170000'],
|
['DTSTART:20380601T120000Z', '20380601T170000'], # noon UTC to 5PM UTC
|
||||||
['TZID=America/New_York:20180601T120000', '20180602T170000'],
|
['DTSTART;TZID=America/New_York:20380601T120000', '20380601T170000'], # noon EST to 5PM EST
|
||||||
])
|
])
|
||||||
def test_tzinfo_naive_until(job_template, dtstart, until):
|
def test_tzinfo_naive_until(job_template, dtstart, until):
|
||||||
rrule = 'DTSTART;{} RRULE:FREQ=DAILY;INTERVAL=1;UNTIL={}'.format(dtstart, until) # noqa
|
rrule = '{} RRULE:FREQ=HOURLY;INTERVAL=1;UNTIL={}'.format(dtstart, until) # noqa
|
||||||
s = Schedule(
|
s = Schedule(
|
||||||
name='Some Schedule',
|
name='Some Schedule',
|
||||||
rrule=rrule,
|
rrule=rrule,
|
||||||
unified_job_template=job_template
|
unified_job_template=job_template
|
||||||
)
|
)
|
||||||
with pytest.raises(ValueError):
|
s.save()
|
||||||
s.save()
|
gen = Schedule.rrulestr(s.rrule).xafter(now(), count=20)
|
||||||
|
assert len(list(gen)) == 6 # noon, 1PM, 2, 3, 4, 5PM
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_until_must_be_utc(job_template):
|
|
||||||
rrule = 'DTSTART;TZID=America/New_York:20180601T120000 RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20180602T000000' # noqa the Z is required
|
|
||||||
s = Schedule(
|
|
||||||
name='Some Schedule',
|
|
||||||
rrule=rrule,
|
|
||||||
unified_job_template=job_template
|
|
||||||
)
|
|
||||||
with pytest.raises(ValueError) as e:
|
|
||||||
s.save()
|
|
||||||
assert 'RRULE UNTIL values must be specified in UTC' in str(e)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -211,7 +200,6 @@ def test_beginning_of_time(job_template):
|
|||||||
['DTSTART;TZID=America/New_York:20300112T210000 RRULE:FREQ=DAILY;INTERVAL=1', 'America/New_York']
|
['DTSTART;TZID=America/New_York:20300112T210000 RRULE:FREQ=DAILY;INTERVAL=1', 'America/New_York']
|
||||||
])
|
])
|
||||||
def test_timezone_property(job_template, rrule, tz):
|
def test_timezone_property(job_template, rrule, tz):
|
||||||
# ensure that really large generators don't have performance issues
|
|
||||||
s = Schedule(
|
s = Schedule(
|
||||||
name='Some Schedule',
|
name='Some Schedule',
|
||||||
rrule=rrule,
|
rrule=rrule,
|
||||||
|
|||||||
Reference in New Issue
Block a user