diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 72430b2637..1158e0cfa3 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -25,6 +25,9 @@ jobs: - 5432/tcp # needed because the postgres container does not provide a healthcheck options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + redis: + image: redis:7.2.3 + options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v2 @@ -59,6 +62,8 @@ jobs: exit $STATUS env: DATABASE_URL: postgresql://postgres:postgres@localhost:${{ job.services.postgres.ports['5432'] }}/postgres + CELERY_BROKER_URL: redis://redis:6379/0 + CELERY_RESULT_BACKEND: redis://redis:6379/1 STRIPE_SECRET_API_KEY: fake-key CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} MEDIA_FILES_STORAGE_BACKEND: django.core.files.storage.FileSystemStorage diff --git a/backend/Dockerfile b/backend/Dockerfile index 05f246f67b..202c833eaf 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -67,7 +67,8 @@ COPY . ${FUNCTION_DIR} ENV DJANGO_SETTINGS_MODULE=pycon.settings.prod -RUN AWS_MEDIA_BUCKET=example \ +RUN DJANGO_SETTINGS_MODULE=pycon.settings.test \ + AWS_MEDIA_BUCKET=example \ AWS_REGION_NAME=eu-central-1 \ SECRET_KEY=DEMO \ STRIPE_SECRET_API_KEY=demo \ diff --git a/backend/cms/components/page/signals.py b/backend/cms/components/page/signals.py index 9b38941c24..2a729a0295 100644 --- a/backend/cms/components/page/signals.py +++ b/backend/cms/components/page/signals.py @@ -1,7 +1,7 @@ -import requests import logging from cms.components.sites.models import VercelFrontendSettings +from cms.components.page.tasks import revalidate_vercel_frontend_task logger = logging.getLogger(__name__) @@ -9,62 +9,14 @@ def revalidate_vercel_frontend(sender, **kwargs): instance = kwargs["instance"] - site = kwargs["instance"].get_site() + site = instance.get_site() if not site: # page doesn't belong to any site return - site_name = site.site_name - hostname = site.hostname settings = VercelFrontendSettings.for_site(site) - if not settings: - # not configured for this site + if not settings.revalidate_url: return - url = settings.revalidate_url - secret = settings.revalidate_secret - - if not url or not secret: - # not configured for this site - return - - language_code = instance.locale.language_code - - if language_code != "en": - # we need to get the original slug - # as we use the english slugs for the frontend - english_page = ( - instance.get_translations(inclusive=True) - .filter(locale__language_code="en") - .first() - ) - - slug = english_page.slug - _, _, page_path = english_page.get_url_parts() - else: - slug = instance.slug - _, _, page_path = instance.get_url_parts() - - page_path = page_path[:-1] - - if slug == hostname: - path = f"/{language_code}" - else: - path = f"/{language_code}{page_path}" - - try: - response = requests.post( - url, - timeout=None, - json={ - "secret": secret, - "path": path, - }, - ) - response.raise_for_status() - except Exception as e: - logger.error(f"Error while revalidating {path} on {site_name}: {e}") - return - - logger.info(f"Revalidated {path} on {site_name}") + revalidate_vercel_frontend_task.delay(page_id=instance.id) diff --git a/backend/cms/components/page/tasks.py b/backend/cms/components/page/tasks.py new file mode 100644 index 0000000000..b090541d2b --- /dev/null +++ b/backend/cms/components/page/tasks.py @@ -0,0 +1,65 @@ +import requests +import logging +from pycon.celery import app +from wagtail.models import Page +from cms.components.sites.models import VercelFrontendSettings + +logger = logging.getLogger(__name__) + + +@app.task +def revalidate_vercel_frontend_task(page_id): + page = Page.objects.get(id=page_id) + site = page.get_site() + + settings = VercelFrontendSettings.for_site(site) + + site_name = site.site_name + hostname = site.hostname + + url = settings.revalidate_url + secret = settings.revalidate_secret + + if not url or not secret: + # not configured for this site + return + + language_code = page.locale.language_code + + if language_code != "en": + # we need to get the original slug + # as we use the english slugs for the frontend + english_page = ( + page.get_translations(inclusive=True) + .filter(locale__language_code="en") + .first() + ) + + slug = english_page.slug + _, _, page_path = english_page.get_url_parts() + else: + slug = page.slug + _, _, page_path = page.get_url_parts() + + page_path = page_path[:-1] + + if slug == hostname: + path = f"/{language_code}" + else: + path = f"/{language_code}{page_path}" + + try: + response = requests.post( + url, + timeout=None, + json={ + "secret": secret, + "path": path, + }, + ) + response.raise_for_status() + except Exception as e: + logger.error(f"Error while revalidating {path} on {site_name}: {e}") + return + + logger.info(f"Revalidated {path} on {site_name}") diff --git a/backend/cms/components/page/tests/test_signals.py b/backend/cms/components/page/tests/test_signals.py index b6281a562f..add67af50a 100644 --- a/backend/cms/components/page/tests/test_signals.py +++ b/backend/cms/components/page/tests/test_signals.py @@ -1,119 +1,35 @@ +from cms.components.sites.tests.factories import VercelFrontendSettingsFactory +from unittest import mock import pytest from cms.components.page.signals import revalidate_vercel_frontend -from cms.components.sites.tests.factories import VercelFrontendSettingsFactory from wagtail_factories import PageFactory, SiteFactory pytestmark = pytest.mark.django_db -def test_revalidate_vercel_frontend_disabled_if_not_configured(requests_mock): - mock_call = requests_mock.post("https://test.com", status_code=200) - +@mock.patch("cms.components.page.signals.revalidate_vercel_frontend_task") +def test_revalidate_vercel_frontend_disabled_if_not_configured(mock_task): site = SiteFactory() page = PageFactory() site.root_page = page site.save() - revalidate_vercel_frontend("test_revalidate_vercel_frontend", instance=page) - - assert not mock_call.called - - -def test_revalidate_vercel_frontend( - requests_mock, -): - site = SiteFactory() - parent = PageFactory() - page = PageFactory(slug="test-page123") - page.set_url_path(parent) - - site.root_page = parent - site.save() - - settings = VercelFrontendSettingsFactory( - revalidate_url="https://test.com", revalidate_secret="test", site=site - ) - mock_call = requests_mock.post(settings.revalidate_url, status_code=200) revalidate_vercel_frontend("test_revalidate_vercel_frontend", instance=page) - assert mock_call.called - body = mock_call.last_request.json() - assert body["secret"] == "test" - assert body["path"] == "/en/test-page123" + mock_task.delay.assert_not_called() -def test_revalidate_vercel_frontend_special_case_for_landing_page( - requests_mock, -): +@mock.patch("cms.components.page.signals.revalidate_vercel_frontend_task") +def test_revalidate_vercel_frontend(mock_task): site = SiteFactory() - - parent = PageFactory() - page = PageFactory(slug=site.hostname) - page.set_url_path(parent) - site.root_page = parent - + page = PageFactory() + site.root_page = page site.save() - settings = VercelFrontendSettingsFactory( + VercelFrontendSettingsFactory( revalidate_url="https://test.com", revalidate_secret="test", site=site ) - mock_call = requests_mock.post(settings.revalidate_url, status_code=200) revalidate_vercel_frontend("test_revalidate_vercel_frontend", instance=page) - assert mock_call.called - - body = mock_call.last_request.json() - assert body["secret"] == "test" - assert body["path"] == "/en" - - -def test_revalidate_vercel_frontend_for_different_language(requests_mock, locale): - parent = PageFactory() - site = SiteFactory(hostname="pycon", root_page=parent) - - page = PageFactory(slug="test123", locale=locale("en"), parent=parent) - page.set_url_path(parent) - - italian_page = page.copy_for_translation(locale=locale("it")) - italian_page.slug = "something-else" - italian_page.save() - italian_page.set_url_path(parent) - - settings = VercelFrontendSettingsFactory( - revalidate_url="https://test.com", revalidate_secret="test", site=site - ) - mock_call = requests_mock.post(settings.revalidate_url, status_code=200) - - revalidate_vercel_frontend("test_revalidate_vercel_frontend", instance=italian_page) - - assert mock_call.called - - body = mock_call.last_request.json() - assert body["secret"] == "test" - assert body["path"] == "/it/test123" - - -def test_revalidate_vercel_frontend_when_vercel_is_down_doesnt_crash( - caplog, - requests_mock, - locale, -): - parent = PageFactory() - - page = PageFactory(slug="test123", locale=locale("en"), parent=parent) - site = SiteFactory(hostname="pycon", root_page=page) - - italian_page = page.copy_for_translation(locale=locale("it")) - italian_page.slug = "something-else" - italian_page.save() - - settings = VercelFrontendSettingsFactory( - revalidate_url="https://test.com", revalidate_secret="test", site=site - ) - mock_call = requests_mock.post(settings.revalidate_url, status_code=500) - - revalidate_vercel_frontend("test_revalidate_vercel_frontend", instance=italian_page) - - assert mock_call.called - assert "Error while revalidating" in caplog.records[0].message + mock_task.delay.assert_called_with(page_id=page.id) diff --git a/backend/cms/components/page/tests/test_tasks.py b/backend/cms/components/page/tests/test_tasks.py new file mode 100644 index 0000000000..6812092c8f --- /dev/null +++ b/backend/cms/components/page/tests/test_tasks.py @@ -0,0 +1,105 @@ +import pytest +from cms.components.page.tasks import revalidate_vercel_frontend_task +from cms.components.sites.tests.factories import VercelFrontendSettingsFactory +from wagtail_factories import SiteFactory, PageFactory +from cms.components.page.models import GenericPage + +pytestmark = pytest.mark.django_db + + +def test_revalidate_vercel_frontend( + requests_mock, +): + site = SiteFactory() + parent = PageFactory() + page = GenericPage(title="Test Page123", slug="test-page123") + parent.add_child(instance=page) + site.root_page = parent + site.save() + + settings = VercelFrontendSettingsFactory( + revalidate_url="https://test.com", revalidate_secret="test", site=site + ) + mock_call = requests_mock.post(settings.revalidate_url, status_code=200) + + revalidate_vercel_frontend_task(page_id=page.id) + + assert mock_call.called + body = mock_call.last_request.json() + assert body["secret"] == "test" + assert body["path"] == "/en/test-page123" + + +def test_revalidate_vercel_frontend_special_case_for_landing_page( + requests_mock, +): + site = SiteFactory() + parent = PageFactory() + page = GenericPage(title="Test Page123", slug=site.hostname) + parent.add_child(instance=page) + site.root_page = parent + site.save() + + settings = VercelFrontendSettingsFactory( + revalidate_url="https://test.com", revalidate_secret="test", site=site + ) + mock_call = requests_mock.post(settings.revalidate_url, status_code=200) + + revalidate_vercel_frontend_task(page_id=page.id) + + assert mock_call.called + + body = mock_call.last_request.json() + assert body["secret"] == "test" + assert body["path"] == "/en" + + +def test_revalidate_vercel_frontend_for_different_language(requests_mock, locale): + parent = PageFactory() + site = SiteFactory(hostname="pycon", root_page=parent) + + page = PageFactory(slug="test123", locale=locale("en"), parent=parent) + page.set_url_path(parent) + + italian_page = page.copy_for_translation(locale=locale("it")) + italian_page.slug = "something-else" + italian_page.save() + italian_page.set_url_path(parent) + + settings = VercelFrontendSettingsFactory( + revalidate_url="https://test.com", revalidate_secret="test", site=site + ) + mock_call = requests_mock.post(settings.revalidate_url, status_code=200) + + revalidate_vercel_frontend_task(page_id=italian_page.id) + + assert mock_call.called + + body = mock_call.last_request.json() + assert body["secret"] == "test" + assert body["path"] == "/it/test123" + + +def test_revalidate_vercel_frontend_when_vercel_is_down_doesnt_crash( + caplog, + requests_mock, + locale, +): + parent = PageFactory() + + page = PageFactory(slug="test123", locale=locale("en"), parent=parent) + site = SiteFactory(hostname="pycon", root_page=page) + + italian_page = page.copy_for_translation(locale=locale("it")) + italian_page.slug = "something-else" + italian_page.save() + + settings = VercelFrontendSettingsFactory( + revalidate_url="https://test.com", revalidate_secret="test", site=site + ) + mock_call = requests_mock.post(settings.revalidate_url, status_code=500) + + revalidate_vercel_frontend_task(page_id=italian_page.id) + + assert mock_call.called + assert "Error while revalidating" in caplog.records[0].message diff --git a/backend/pdm.lock b/backend/pdm.lock index 3de9e47a5b..f330e1da54 100644 --- a/backend/pdm.lock +++ b/backend/pdm.lock @@ -5,7 +5,20 @@ groups = ["default", "dev", "lambda"] strategy = ["cross_platform"] lock_version = "4.4" -content_hash = "sha256:fb4d75871091d91350ba1a0c115d525a78a48832826027f4936ac957575ddaf9" +content_hash = "sha256:0086f8f8392976cc6c44e7eb11554fcb04cd283c6806400fcc4ee1ef912db044" + +[[package]] +name = "amqp" +version = "5.2.0" +requires_python = ">=3.6" +summary = "Low-level AMQP client for Python (fork of amqplib)." +dependencies = [ + "vine<6.0.0,>=5.0.0", +] +files = [ + {file = "amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"}, + {file = "amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"}, +] [[package]] name = "aniso8601" @@ -219,6 +232,16 @@ files = [ {file = "beautifulsoup4-4.11.2.tar.gz", hash = "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"}, ] +[[package]] +name = "billiard" +version = "4.2.0" +requires_python = ">=3.7" +summary = "Python multiprocessing fork with improvements and bugfixes" +files = [ + {file = "billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d"}, + {file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"}, +] + [[package]] name = "black" version = "23.10.1" @@ -271,22 +294,22 @@ files = [ [[package]] name = "boto3" -version = "1.29.6" +version = "1.33.11" requires_python = ">= 3.7" summary = "The AWS SDK for Python" dependencies = [ - "botocore<1.33.0,>=1.32.6", + "botocore<1.34.0,>=1.33.11", "jmespath<2.0.0,>=0.7.1", - "s3transfer<0.8.0,>=0.7.0", + "s3transfer<0.9.0,>=0.8.2", ] files = [ - {file = "boto3-1.29.6-py3-none-any.whl", hash = "sha256:f4d19e01d176c3a5a05e4af733185ff1891b08a3c38d4a439800fa132aa6e9be"}, - {file = "boto3-1.29.6.tar.gz", hash = "sha256:d1d0d979a70bf9b0b13ae3b017f8523708ad953f62d16f39a602d67ee9b25554"}, + {file = "boto3-1.33.11-py3-none-any.whl", hash = "sha256:8d54fa3a9290020f9a7f488f9cbe821029de0af05a677751b12973a5f726a5e2"}, + {file = "boto3-1.33.11.tar.gz", hash = "sha256:620f1eb3e18e780be58383b4a4e10db003d2314131190514153996032c8d932d"}, ] [[package]] name = "botocore" -version = "1.32.6" +version = "1.33.11" requires_python = ">= 3.7" summary = "Low-level, data-driven core of boto 3." dependencies = [ @@ -295,8 +318,8 @@ dependencies = [ "urllib3<2.1,>=1.25.4; python_version >= \"3.10\"", ] files = [ - {file = "botocore-1.32.6-py3-none-any.whl", hash = "sha256:4454f967a4d1a01e3e6205c070455bc4e8fd53b5b0753221581ae679c55a9dfd"}, - {file = "botocore-1.32.6.tar.gz", hash = "sha256:ecec876103783b5efe6099762dda60c2af67e45f7c0ab4568e8265d11c6c449b"}, + {file = "botocore-1.33.11-py3-none-any.whl", hash = "sha256:b46227eb3fa9cfdc8f5a83920ef347e67adea8095830ed265a3373b13b54421f"}, + {file = "botocore-1.33.11.tar.gz", hash = "sha256:b14b328f902d120de0a09eaa657a9a701c0ceeb711197c2f01ef0523f855086c"}, ] [[package]] @@ -309,6 +332,36 @@ files = [ {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, ] +[[package]] +name = "celery" +version = "5.3.6" +requires_python = ">=3.8" +summary = "Distributed Task Queue." +dependencies = [ + "billiard<5.0,>=4.2.0", + "click-didyoumean>=0.3.0", + "click-plugins>=1.1.1", + "click-repl>=0.2.0", + "click<9.0,>=8.1.2", + "kombu<6.0,>=5.3.4", + "python-dateutil>=2.8.2", + "tzdata>=2022.7", + "vine<6.0,>=5.1.0", +] +files = [ + {file = "celery-5.3.6-py3-none-any.whl", hash = "sha256:9da4ea0118d232ce97dff5ed4974587fb1c0ff5c10042eb15278487cdd27d1af"}, + {file = "celery-5.3.6.tar.gz", hash = "sha256:870cc71d737c0200c397290d730344cc991d13a057534353d124c9380267aab9"}, +] + +[[package]] +name = "cerberus" +version = "1.3.5" +summary = "Lightweight, extensible schema and data validation tool for Pythondictionaries." +files = [ + {file = "Cerberus-1.3.5-py3-none-any.whl", hash = "sha256:7649a5815024d18eb7c6aa5e7a95355c649a53aacfc9b050e9d0bf6bfa2af372"}, + {file = "Cerberus-1.3.5.tar.gz", hash = "sha256:81011e10266ef71b6ec6d50e60171258a5b134d69f8fb387d16e4936d0d47642"}, +] + [[package]] name = "certifi" version = "2021.10.8" @@ -383,6 +436,45 @@ files = [ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] +[[package]] +name = "click-didyoumean" +version = "0.3.0" +requires_python = ">=3.6.2,<4.0.0" +summary = "Enables git-like *did-you-mean* feature in click" +dependencies = [ + "click>=7", +] +files = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] + +[[package]] +name = "click-plugins" +version = "1.1.1" +summary = "An extension module for click to enable registering CLI commands via setuptools entry-points." +dependencies = [ + "click>=4.0", +] +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +requires_python = ">=3.6" +summary = "REPL plugin for Click" +dependencies = [ + "click>=7.0", + "prompt-toolkit>=3.0.36", +] +files = [ + {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, + {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -470,36 +562,36 @@ files = [ [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" requires_python = ">=3.7" summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." dependencies = [ "cffi>=1.12", ] files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [[package]] @@ -544,6 +636,15 @@ files = [ {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, ] +[[package]] +name = "distlib" +version = "0.3.7" +summary = "Distribution utilities" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + [[package]] name = "django" version = "4.2.7" @@ -595,15 +696,15 @@ files = [ [[package]] name = "django-filter" -version = "23.4" +version = "23.5" requires_python = ">=3.7" summary = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." dependencies = [ "Django>=3.2", ] files = [ - {file = "django-filter-23.4.tar.gz", hash = "sha256:bed070b38359dce7d2dbe057b165d59773057986356cb809ded983b36c77a976"}, - {file = "django_filter-23.4-py3-none-any.whl", hash = "sha256:526954f18bd7d6423f232a9a7974f58fbc6863908b9fc160de075e01adcc2a5f"}, + {file = "django-filter-23.5.tar.gz", hash = "sha256:67583aa43b91fe8c49f74a832d95f4d8442be628fd4c6d65e9f811f5153a4e5c"}, + {file = "django_filter-23.5-py3-none-any.whl", hash = "sha256:99122a201d83860aef4fe77758b69dda913e874cc5e0eaa50a86b0b18d708400"}, ] [[package]] @@ -631,7 +732,7 @@ files = [ [[package]] name = "django-import-export" -version = "3.3.3" +version = "3.3.4" requires_python = ">=3.8" summary = "Django application and library for importing and exporting data with included admin integration." dependencies = [ @@ -640,8 +741,8 @@ dependencies = [ "tablib[html,ods,xls,xlsx,yaml]==3.5.0", ] files = [ - {file = "django-import-export-3.3.3.tar.gz", hash = "sha256:2c1b16e1cf2ea5f62a165d8867e7c6dcff25673ab7201fd18aaf67c9ee90367e"}, - {file = "django_import_export-3.3.3-py3-none-any.whl", hash = "sha256:78973202e93897326ab0411d64eaf89b72779fcb21ee9e5f64f3fb96571a5978"}, + {file = "django-import-export-3.3.4.tar.gz", hash = "sha256:c82c0dc40d308177ee6a6a263e1652da1a84df62482af6695bfb9f8df921fceb"}, + {file = "django_import_export-3.3.4-py3-none-any.whl", hash = "sha256:46280cc07a2750b6fc869ac2bb07c09a152d8ccf98a76f5bd384944bde627693"}, ] [[package]] @@ -816,6 +917,14 @@ files = [ {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] +[[package]] +name = "docopt" +version = "0.6.2" +summary = "Pythonic argument parser, that will make you smile" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] + [[package]] name = "draftjs-exporter" version = "2.1.7" @@ -919,7 +1028,7 @@ files = [ [[package]] name = "google-api-core" -version = "2.14.0" +version = "2.15.0" requires_python = ">=3.7" summary = "Google API client core library" dependencies = [ @@ -929,13 +1038,13 @@ dependencies = [ "requests<3.0.0.dev0,>=2.18.0", ] files = [ - {file = "google-api-core-2.14.0.tar.gz", hash = "sha256:5368a4502b793d9bbf812a5912e13e4e69f9bd87f6efb508460c43f5bbd1ce41"}, - {file = "google_api_core-2.14.0-py3-none-any.whl", hash = "sha256:de2fb50ed34d47ddbb2bd2dcf680ee8fead46279f4ed6b16de362aca23a18952"}, + {file = "google-api-core-2.15.0.tar.gz", hash = "sha256:abc978a72658f14a2df1e5e12532effe40f94f868f6e23d95133bd6abcca35ca"}, + {file = "google_api_core-2.15.0-py3-none-any.whl", hash = "sha256:2aa56d2be495551e66bbff7f729b790546f87d5c90e74781aa77233bcb395a8a"}, ] [[package]] name = "google-api-python-client" -version = "2.108.0" +version = "2.110.0" requires_python = ">=3.7" summary = "Google API Client Library for Python" dependencies = [ @@ -946,13 +1055,13 @@ dependencies = [ "uritemplate<5,>=3.0.1", ] files = [ - {file = "google-api-python-client-2.108.0.tar.gz", hash = "sha256:6396efca83185fb205c0abdbc1c2ee57b40475578c6af37f6d0e30a639aade99"}, - {file = "google_api_python_client-2.108.0-py2.py3-none-any.whl", hash = "sha256:9d1327213e388943ebcd7db5ce6e7f47987a7e6874e3e1f6116010eea4a0e75d"}, + {file = "google-api-python-client-2.110.0.tar.gz", hash = "sha256:1f825e48c7fdc3c96ad6aac179cb73c3755dfff41d16487fa7130e5efcfe7b76"}, + {file = "google_api_python_client-2.110.0-py2.py3-none-any.whl", hash = "sha256:55e7ebd6079e34934b6751537eb13447110351ae3792a724a33825d7b671ba13"}, ] [[package]] name = "google-auth" -version = "2.23.4" +version = "2.25.2" requires_python = ">=3.7" summary = "Google Authentication Library" dependencies = [ @@ -961,8 +1070,8 @@ dependencies = [ "rsa<5,>=3.1.4", ] files = [ - {file = "google-auth-2.23.4.tar.gz", hash = "sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3"}, - {file = "google_auth-2.23.4-py2.py3-none-any.whl", hash = "sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2"}, + {file = "google-auth-2.25.2.tar.gz", hash = "sha256:42f707937feb4f5e5a39e6c4f343a17300a459aaf03141457ba505812841cc40"}, + {file = "google_auth-2.25.2-py2.py3-none-any.whl", hash = "sha256:473a8dfd0135f75bb79d878436e568f2695dce456764bf3a02b6f8c540b1d256"}, ] [[package]] @@ -994,15 +1103,15 @@ files = [ [[package]] name = "googleapis-common-protos" -version = "1.61.0" +version = "1.62.0" requires_python = ">=3.7" summary = "Common protobufs used in Google APIs" dependencies = [ "protobuf!=3.20.0,!=3.20.1,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0.dev0,>=3.19.5", ] files = [ - {file = "googleapis-common-protos-1.61.0.tar.gz", hash = "sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b"}, - {file = "googleapis_common_protos-1.61.0-py2.py3-none-any.whl", hash = "sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0"}, + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, ] [[package]] @@ -1176,12 +1285,17 @@ files = [ [[package]] name = "isort" -version = "5.12.0" +version = "5.13.0" requires_python = ">=3.8.0" summary = "A Python utility / library to sort Python imports." +dependencies = [ + "pip-api", + "pipreqs", + "requirementslib", +] files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.0-py3-none-any.whl", hash = "sha256:15e0e937819b350bc256a7ae13bb25f4fe4f8871a0bc335b20c3627dba33f458"}, + {file = "isort-5.13.0.tar.gz", hash = "sha256:d67f78c6a1715f224cca46b29d740037bdb6eea15323a133e897cda15876147b"}, ] [[package]] @@ -1209,6 +1323,20 @@ files = [ {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, ] +[[package]] +name = "kombu" +version = "5.3.4" +requires_python = ">=3.8" +summary = "Messaging library for Python." +dependencies = [ + "amqp<6.0.0,>=5.1.1", + "vine", +] +files = [ + {file = "kombu-5.3.4-py3-none-any.whl", hash = "sha256:63bb093fc9bb80cfb3a0972336a5cec1fa7ac5f9ef7e8237c6bf8dda9469313e"}, + {file = "kombu-5.3.4.tar.gz", hash = "sha256:0bb2e278644d11dea6272c17974a3dbb9688a949f3bb60aeb5b791329c44fadc"}, +] + [[package]] name = "l18n" version = "2021.3" @@ -1339,31 +1467,33 @@ files = [ [[package]] name = "msal" -version = "1.25.0" +version = "1.26.0" requires_python = ">=2.7" -summary = "The Microsoft Authentication Library (MSAL) for Python library" +summary = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." dependencies = [ "PyJWT[crypto]<3,>=1.0.0", "cryptography<44,>=0.6", "requests<3,>=2.0.0", ] files = [ - {file = "msal-1.25.0-py2.py3-none-any.whl", hash = "sha256:386df621becb506bc315a713ec3d4d5b5d6163116955c7dde23622f156b81af6"}, - {file = "msal-1.25.0.tar.gz", hash = "sha256:f44329fdb59f4f044c779164a34474b8a44ad9e4940afbc4c3a3a2bbe90324d9"}, + {file = "msal-1.26.0-py2.py3-none-any.whl", hash = "sha256:be77ba6a8f49c9ff598bbcdc5dfcf1c9842f3044300109af738e8c3e371065b5"}, + {file = "msal-1.26.0.tar.gz", hash = "sha256:224756079fe338be838737682b49f8ebc20a87c1c5eeaf590daae4532b83de15"}, ] [[package]] name = "msal-extensions" -version = "1.0.0" +version = "1.1.0" +requires_python = ">=3.7" summary = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." dependencies = [ "msal<2.0.0,>=0.4.1", - "portalocker<3,>=1.0; python_version >= \"3.5\" and platform_system != \"Windows\"", - "portalocker<3,>=1.6; python_version >= \"3.5\" and platform_system == \"Windows\"", + "packaging", + "portalocker<3,>=1.0; platform_system != \"Windows\"", + "portalocker<3,>=1.6; platform_system == \"Windows\"", ] files = [ - {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, - {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, + {file = "msal-extensions-1.1.0.tar.gz", hash = "sha256:6ab357867062db7b253d0bd2df6d411c7891a0ee7308d54d1e4317c1d1c54252"}, + {file = "msal_extensions-1.1.0-py3-none-any.whl", hash = "sha256:01be9711b4c0b1a151450068eeb2c4f0997df3bba085ac299de3a66f585e382f"}, ] [[package]] @@ -1498,12 +1628,12 @@ files = [ [[package]] name = "pathspec" -version = "0.11.2" -requires_python = ">=3.7" +version = "0.12.1" +requires_python = ">=3.8" summary = "Utility library for gitignore style pattern matching of file paths." files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] @@ -1520,6 +1650,16 @@ files = [ {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, ] +[[package]] +name = "pep517" +version = "0.13.1" +requires_python = ">=3.6" +summary = "Wrappers to build Python packages using PEP 517 hooks" +files = [ + {file = "pep517-0.13.1-py3-none-any.whl", hash = "sha256:31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721"}, + {file = "pep517-0.13.1.tar.gz", hash = "sha256:1b2fa2ffd3938bb4beffe5d6146cbcb2bda996a5a4da9f31abffd8b24e07b317"}, +] + [[package]] name = "pilkit" version = "3.0" @@ -1569,53 +1709,118 @@ files = [ [[package]] name = "pillow-heif" -version = "0.13.1" +version = "0.14.0" requires_python = ">=3.8" summary = "Python interface for libheif library" dependencies = [ - "pillow>=9.1.1", -] -files = [ - {file = "pillow_heif-0.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:779af08dc4dc0e787608ef035a525de40bf98e946ab194e3f702510c07af5f80"}, - {file = "pillow_heif-0.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:df4eba9bd64bb50656eda31acddac58f53bc3f2c5b35ea8c90438a7e2763bce7"}, - {file = "pillow_heif-0.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b586ba25c003bc2b608fe5cd1396b738b838ad4277348a5ca4525aac1f28e7b9"}, - {file = "pillow_heif-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5389574c874805bd6a3f2d5ef14ef90b61fdbebd75b58798677e84018caac6c1"}, - {file = "pillow_heif-0.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:736800774f9754ebfeba21ae137db0aa9f19df17521aee70ceeb68e569a5fb00"}, - {file = "pillow_heif-0.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86f47db53eb719bccddb14cadd91fe9fa38b57aa07e3823ffe74b796a2bc50f2"}, - {file = "pillow_heif-0.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:4f0eb2babec34900367384feaacff9a2f42e959767ba5df214560ef579b657b1"}, - {file = "pillow_heif-0.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14787322b70e9cb02e599ef836f33181f981383f4848206f9fe5a272821806cb"}, - {file = "pillow_heif-0.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f483c68895faad5af313e30c546cd935389ee99e59522d12f53225b46dcb9753"}, - {file = "pillow_heif-0.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae9169e1ec94793d1b9ffe98ee39f856ca002e3ac476eaef0b524fedc81d2304"}, - {file = "pillow_heif-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e11c2a564663ce52f56851f3b9653abb254ec520191d3cf7841fcd02c29e3a"}, - {file = "pillow_heif-0.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:023ec7a6f085be0cf33e1b4458575579e3952da36b9c32956dbb2749b53e63b3"}, - {file = "pillow_heif-0.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459b8536e86df2f957998f9ba830ae3e8a51177db67d1a9cb7243402bb70b576"}, - {file = "pillow_heif-0.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:6fa3661f6f8c6c28e4e565c7aa9e4a1ae2507aa1f3691f831d3a9bfd465ceea4"}, - {file = "pillow_heif-0.13.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7651354c30a4e2fb075df2a0e48ae3193a0b378ee2c262dea63b5b3013ec9b5"}, - {file = "pillow_heif-0.13.1-pp310-pypy310_pp73-macosx_12_0_arm64.whl", hash = "sha256:9998235d3f1edfec3691161dbe83501b7139840d80caef640a9c87d7d5934b69"}, - {file = "pillow_heif-0.13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c8f070fe3705a52ef34c6b8cd39c29529972b5d7e2388a80530421e6d042b38"}, - {file = "pillow_heif-0.13.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b6ab214fa5ec6f709ec79b7bc2abaecba1553849ef0f04cd8ab1cf3b0aaf94"}, - {file = "pillow_heif-0.13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2fd56909022dfff4e02397585dda6b3ae25cd11adf1123c5617224947734205b"}, - {file = "pillow_heif-0.13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b3bd97b4ce968a776eb04c1085f20834d100021407398d1ea6db2c9fa4167fed"}, - {file = "pillow_heif-0.13.1-pp38-pypy38_pp73-macosx_12_0_arm64.whl", hash = "sha256:c4f6125b5d3d18a17e43318ceaadc0eaad12ec37948bb7cc3ffee016c9d62ea4"}, - {file = "pillow_heif-0.13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a9763fb132b606c3fc1f749f3c4b94e01453a96366b9746d61c431626af12"}, - {file = "pillow_heif-0.13.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441751e7a09d4f253f48a4076fdf4778eea6d90fd8a57f0afde27236ba535eb1"}, - {file = "pillow_heif-0.13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6edc64c8e7172364bbeff09745cd76e443e673f68bd84708904dcdedab2645fd"}, - {file = "pillow_heif-0.13.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f61ebf35ad81994fcde3e7a7ebd15adc9dc9419aab97c6e4a535c04940daa20"}, - {file = "pillow_heif-0.13.1-pp39-pypy39_pp73-macosx_12_0_arm64.whl", hash = "sha256:a7745c019f954aab611299908089e3e53f45ed4eda057cb32782b90cbf5c1ef9"}, - {file = "pillow_heif-0.13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99235d323388de1bef001f06f6a35f20a1d6954896d79e44f4dc4d2bb6f3229c"}, - {file = "pillow_heif-0.13.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e360c0c533345b34e05b923b49abbf08ec7ebe8e17f6cca6b446d386b7fd8b3"}, - {file = "pillow_heif-0.13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9cf0b1a18e660b829fb9c618b5539c3c741301009ff06e8438b89211e890a13"}, - {file = "pillow_heif-0.13.1.tar.gz", hash = "sha256:0d46adc8a8afb515297fd47fc0b15657b8927f7ec52ad1fb8d63250413d4c6ba"}, + "pillow>=9.2.0", +] +files = [ + {file = "pillow_heif-0.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6175139a455a53fc9160e8879c5d6721753f23a88eaeb0b2fdae2b608963b39"}, + {file = "pillow_heif-0.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bda81d95e5c7ff31936ebf9c662d23519d0795a143b5c87f93e847d8719f2a41"}, + {file = "pillow_heif-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9f56db0148755a47bf8eba289f73c431aa924cb1e48e1d435cda2cae683f240"}, + {file = "pillow_heif-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:900e37b9ccf18b6d3560c3900b2c9f00848fd27421421b8c642e0465fe7ce5ce"}, + {file = "pillow_heif-0.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40608555f754082488e429eaf09560e48a7ef7dfda322967d133e83650200b12"}, + {file = "pillow_heif-0.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:598f2e2b33f11680ed95621b7a0e2fb5b2a48085ff98fee9ae8bc402310d833e"}, + {file = "pillow_heif-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:467893e0749d52aa0f9eab277173dfc11e79692db69c44279929e24ca1bb3397"}, + {file = "pillow_heif-0.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3042355d5232ed4ea4f6735a287ac5360b1889d94c1e0efabeee5c6ed2b3685e"}, + {file = "pillow_heif-0.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:62b0bdfee9bbc108427b7e96afe7db252cc09104e295a41dec7c80b3d3e5a007"}, + {file = "pillow_heif-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a0f89b6c2be22e27e9c468aabd3f91b628aaf52d21ba57e5d7ca2e23683c571"}, + {file = "pillow_heif-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbee75cdeeae1d7bb93ca2dcc989ce77e1cdeb93c7d19d6767d89ae5e30a4cab"}, + {file = "pillow_heif-0.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3a2ac345804f0069096b3ac85718d81543bb589e418fa0c3fa0fd3822bd45422"}, + {file = "pillow_heif-0.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c95b24f6bff24bc7a0fc81ac15bf353daa04f7e7558c6db24f13ad3bdc3c5263"}, + {file = "pillow_heif-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:9945acf344966cc1db34eb47a8c2de03a2853c5848fdb202c7e9507b4069cd8d"}, + {file = "pillow_heif-0.14.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:68491db1588da4fd143b34a44f3b8da57773c183538b27aaf2b6241b1127dda9"}, + {file = "pillow_heif-0.14.0-pp310-pypy310_pp73-macosx_12_0_arm64.whl", hash = "sha256:8828dfc24e5c6bfd1a3ec2c0b1298b480158cf7b0eb8bfd059802dd40344bb2a"}, + {file = "pillow_heif-0.14.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41babd2c594037f051dae62a9d1ddee2c1a355a79c36c483ace4e80d6e41b7ef"}, + {file = "pillow_heif-0.14.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19cbedb2d73ac9b6ebd20efb366ec492e737af5750bd5475eee048d4d34902d"}, + {file = "pillow_heif-0.14.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:75a96333785c04c2ddc721f539a97cf1082dd49213701328537268386a0d11c7"}, + {file = "pillow_heif-0.14.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:04e2bb02f91fe5b3746f5f7a6207a9f314ecb426631153e857b3a4fa288d2333"}, + {file = "pillow_heif-0.14.0-pp38-pypy38_pp73-macosx_12_0_arm64.whl", hash = "sha256:8ed4bf43a0e18c3527257033d5dd267c869b502955242a8697dc4a6fb2b36b7e"}, + {file = "pillow_heif-0.14.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74300285727e3b25c007640135de465037c92ab2b93ae591ab8ed827ed0df08"}, + {file = "pillow_heif-0.14.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de0ad1560855aafbd48561ccd36c03dfa52e8335a89430325eb233702e913631"}, + {file = "pillow_heif-0.14.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:689f12ac188fabbfbadb9074c6c3521cce0709550ff13ce65f9b701b7b58fc3f"}, + {file = "pillow_heif-0.14.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b2def20791d67e5acce5dc8b3762b5d08fac4b60df3ce60c6b0e5880e3d2c0f"}, + {file = "pillow_heif-0.14.0-pp39-pypy39_pp73-macosx_12_0_arm64.whl", hash = "sha256:5abf80c3d9af74a9ed2795ecca31843620dad1ab5a91281fc19746e903cebe57"}, + {file = "pillow_heif-0.14.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9115e167c5d722c0f9b1e97040b35bb57d66abf42501379f2a8adb0dd49e4c2"}, + {file = "pillow_heif-0.14.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60d8cd69c4f1a168e85b5b6e311ae8b7a09810525760170231a992bf102a08d4"}, + {file = "pillow_heif-0.14.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6f2a93e7bbd3e2183e05a98bc3b8799c5b61e8294ef79a587e607228c7e8015d"}, + {file = "pillow_heif-0.14.0.tar.gz", hash = "sha256:7abd0279f0ec8b5ba1513dc586e52173666ed9278b38e219bf47da92704262cb"}, +] + +[[package]] +name = "pip" +version = "23.3.1" +requires_python = ">=3.7" +summary = "The PyPA recommended tool for installing Python packages." +files = [ + {file = "pip-23.3.1-py3-none-any.whl", hash = "sha256:55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b"}, + {file = "pip-23.3.1.tar.gz", hash = "sha256:1fcaa041308d01f14575f6d0d2ea4b75a3e2871fe4f9c694976f908768e14174"}, ] [[package]] -name = "platformdirs" -version = "4.0.0" +name = "pip-api" +version = "0.0.30" requires_python = ">=3.7" +summary = "An unofficial, importable pip API" +dependencies = [ + "pip", +] +files = [ + {file = "pip-api-0.0.30.tar.gz", hash = "sha256:a05df2c7aa9b7157374bcf4273544201a0c7bae60a9c65bcf84f3959ef3896f3"}, + {file = "pip_api-0.0.30-py3-none-any.whl", hash = "sha256:2a0314bd31522eb9ffe8a99668b0d07fee34ebc537931e7b6483001dbedcbdc9"}, +] + +[[package]] +name = "pipreqs" +version = "0.4.13" +requires_python = ">=3.7" +summary = "Pip requirements.txt generator based on imports in project" +dependencies = [ + "docopt", + "yarg", +] +files = [ + {file = "pipreqs-0.4.13-py2.py3-none-any.whl", hash = "sha256:e522b9ed54aa3e8b7978ff251ab7a9af2f75d2cd8de4c102e881b666a79a308e"}, + {file = "pipreqs-0.4.13.tar.gz", hash = "sha256:a17f167880b6921be37533ce4c81ddc6e22b465c107aad557db43b1add56a99b"}, +] + +[[package]] +name = "platformdirs" +version = "4.1.0" +requires_python = ">=3.8" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, +] + +[[package]] +name = "plette" +version = "0.4.4" +requires_python = ">=3.7" +summary = "Structured Pipfile and Pipfile.lock models." +dependencies = [ + "tomlkit", +] +files = [ + {file = "plette-0.4.4-py2.py3-none-any.whl", hash = "sha256:42d68ce8c6b966874b68758d87d7f20fcff2eff0d861903eea1062126be4d98f"}, + {file = "plette-0.4.4.tar.gz", hash = "sha256:06b8c09eb90293ad0b8101cb5c95c4ea53e9b2b582901845d0904ff02d237454"}, +] + +[[package]] +name = "plette" +version = "0.4.4" +extras = ["validation"] +requires_python = ">=3.7" +summary = "Structured Pipfile and Pipfile.lock models." +dependencies = [ + "cerberus", + "plette==0.4.4", +] +files = [ + {file = "plette-0.4.4-py2.py3-none-any.whl", hash = "sha256:42d68ce8c6b966874b68758d87d7f20fcff2eff0d861903eea1062126be4d98f"}, + {file = "plette-0.4.4.tar.gz", hash = "sha256:06b8c09eb90293ad0b8101cb5c95c4ea53e9b2b582901845d0904ff02d237454"}, ] [[package]] @@ -1650,6 +1855,19 @@ files = [ {file = "portalocker-2.8.2.tar.gz", hash = "sha256:2b035aa7828e46c58e9b31390ee1f169b98e1066ab10b9a6a861fe7e25ee4f33"}, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.41" +requires_python = ">=3.7.0" +summary = "Library for building powerful interactive command lines in Python" +dependencies = [ + "wcwidth", +] +files = [ + {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, + {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, +] + [[package]] name = "protobuf" version = "4.25.1" @@ -2198,6 +2416,27 @@ files = [ {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, ] +[[package]] +name = "requirementslib" +version = "3.0.0" +requires_python = ">=3.7" +summary = "A tool for converting between pip-style and pipfile requirements." +dependencies = [ + "distlib>=0.2.8", + "pep517>=0.5.0", + "pip>=23.1", + "platformdirs", + "plette[validation]", + "pydantic", + "requests", + "setuptools>=40.8", + "tomlkit>=0.5.3", +] +files = [ + {file = "requirementslib-3.0.0-py2.py3-none-any.whl", hash = "sha256:67b42903d7c32f89c7047d1020c619d37cb515c475a4ae6f4e5683e1c56d7bf7"}, + {file = "requirementslib-3.0.0.tar.gz", hash = "sha256:28f8e0b1c38b34ae06de68ef115b03bbcdcdb99f9e9393333ff06ded443e3f24"}, +] + [[package]] name = "rich" version = "13.7.0" @@ -2227,15 +2466,15 @@ files = [ [[package]] name = "s3transfer" -version = "0.7.0" +version = "0.8.2" requires_python = ">= 3.7" summary = "An Amazon S3 Transfer Manager" dependencies = [ - "botocore<2.0a.0,>=1.12.36", + "botocore<2.0a.0,>=1.33.2", ] files = [ - {file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"}, - {file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"}, + {file = "s3transfer-0.8.2-py3-none-any.whl", hash = "sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76"}, + {file = "s3transfer-0.8.2.tar.gz", hash = "sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283"}, ] [[package]] @@ -2340,7 +2579,7 @@ files = [ [[package]] name = "stripe" -version = "7.6.0" +version = "7.8.1" requires_python = ">=3.6" summary = "Python bindings for the Stripe API" dependencies = [ @@ -2348,8 +2587,8 @@ dependencies = [ "typing-extensions>=4.5.0; python_version >= \"3.7\"", ] files = [ - {file = "stripe-7.6.0-py2.py3-none-any.whl", hash = "sha256:7bd0c0af5426a9842a3587a63265b916a3213395ed9c34f16c9f1ce365937357"}, - {file = "stripe-7.6.0.tar.gz", hash = "sha256:69e6b80f76f9587c8326e43232d2e9f24d2129a61b995d5a14deb3813048896a"}, + {file = "stripe-7.8.1-py2.py3-none-any.whl", hash = "sha256:1889786b43d46b9fc5f70cdd7c16ef6d7e584ddc0440bfeb3e3e68a1aecd3510"}, + {file = "stripe-7.8.1.tar.gz", hash = "sha256:9c16227c075144d8f623de6a649c669ec575db0d13a354a3ca3f4cb21fbe8b59"}, ] [[package]] @@ -2494,12 +2733,12 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] @@ -2563,6 +2802,16 @@ files = [ {file = "uwsgi-2.0.21.tar.gz", hash = "sha256:35a30d83791329429bc04fe44183ce4ab512fcf6968070a7bfba42fc5a0552a9"}, ] +[[package]] +name = "vine" +version = "5.1.0" +requires_python = ">=3.6" +summary = "Python promises." +files = [ + {file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"}, + {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, +] + [[package]] name = "wagtail" version = "5.1.3" @@ -2621,6 +2870,15 @@ files = [ {file = "wagtail_localize-1.5.2.tar.gz", hash = "sha256:52b293228c25518d1a793a8a4406a7ad779be35c7f0ede2932cb0eb35eb8747a"}, ] +[[package]] +name = "wcwidth" +version = "0.2.12" +summary = "Measures the displayed width of unicode strings in a terminal" +files = [ + {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, + {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, +] + [[package]] name = "webencodings" version = "0.5.1" @@ -2711,3 +2969,15 @@ files = [ {file = "xlwt-1.3.0-py2.py3-none-any.whl", hash = "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e"}, {file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"}, ] + +[[package]] +name = "yarg" +version = "0.1.9" +summary = "A semi hard Cornish cheese, also queries PyPI (PyPI client)" +dependencies = [ + "requests", +] +files = [ + {file = "yarg-0.1.9-py2.py3-none-any.whl", hash = "sha256:4f9cebdc00fac946c9bf2783d634e538a71c7d280a4d806d45fd4dc0ef441492"}, + {file = "yarg-0.1.9.tar.gz", hash = "sha256:55695bf4d1e3e7f756496c36a69ba32c40d18f821e38f61d028f6049e5e15911"}, +] diff --git a/backend/pycon/__init__.py b/backend/pycon/__init__.py index e69de29bb2..53f4ccb1d8 100644 --- a/backend/pycon/__init__.py +++ b/backend/pycon/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/backend/pycon/celery.py b/backend/pycon/celery.py new file mode 100644 index 0000000000..22f4b61b19 --- /dev/null +++ b/backend/pycon/celery.py @@ -0,0 +1,8 @@ +import os +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pycon.settings.prod") + +app = Celery("pycon") +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() diff --git a/backend/pycon/settings/dev.py b/backend/pycon/settings/dev.py index 3930d58106..1f42f7b39c 100644 --- a/backend/pycon/settings/dev.py +++ b/backend/pycon/settings/dev.py @@ -1,5 +1,11 @@ from .base import * # noqa +from .base import env SECRET_KEY = "do not use this in production" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +CELERY_TASK_ALWAYS_EAGER = True + +CELERY_BROKER_URL = env("CELERY_BROKER_URL") +CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND") diff --git a/backend/pycon/settings/prod.py b/backend/pycon/settings/prod.py index f74d1c6313..22e8f602e5 100644 --- a/backend/pycon/settings/prod.py +++ b/backend/pycon/settings/prod.py @@ -40,3 +40,6 @@ "https://admin.pycon.it", "https://pycon.it", ] + +CELERY_BROKER_URL = env("CELERY_BROKER_URL") +CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND") diff --git a/backend/pycon/settings/test.py b/backend/pycon/settings/test.py index 7565b6b185..1a2d15db05 100644 --- a/backend/pycon/settings/test.py +++ b/backend/pycon/settings/test.py @@ -31,3 +31,6 @@ } } PYTHONIT_EMAIL_BACKEND = "conftest.TestEmailBackend" + +CELERY_TASK_ALWAYS_EAGER = True +CELERY_TASK_EAGER_PROPAGATES = True diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 2f24fcc1af..c25b7cc9c0 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -83,7 +83,7 @@ dependencies = [ "google-auth<3.0.0,>=2.22.0", "google-auth-oauthlib<2.0.0,>=1.0.0", "google-auth-httplib2<1.0.0,>=0.1.0", - "temporalio<2.0.0,>=1.2.0", + "temporalio==1.4.0", "opencv-python<5.0.0.0,>=4.8.0.74", "argon2-cffi<24.0.0,>=23.1.0", "stripe<8.0.0,>=7.0.0", @@ -91,6 +91,7 @@ dependencies = [ "l18n<2022.0,>=2021.3", "wagtail==5.1.3", "wagtail-localize==1.5.2", + "celery>=5.3.6", ] name = "backend" version = "0.1.0" diff --git a/docker-compose.yml b/docker-compose.yml index 5d802704a1..358a13a38c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,8 @@ x-defaults: DEEPL_AUTH_KEY: ${DEEPL_AUTH_KEY:-} FLODESK_API_KEY: ${FLODESK_API_KEY} FLODESK_SEGMENT_ID: ${FLODESK_SEGMENT_ID} + CELERY_BROKER_URL: redis://redis:6379/9 + CELERY_RESULT_BACKEND: redis://redis:6379/10 services: pycon-backend: diff --git a/infrastructure/applications/.terraform.lock.hcl b/infrastructure/applications/.terraform.lock.hcl index b02275989b..3627119925 100644 --- a/infrastructure/applications/.terraform.lock.hcl +++ b/infrastructure/applications/.terraform.lock.hcl @@ -2,40 +2,44 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "3.61.0" - constraints = "3.61.0" + version = "5.31.0" + constraints = "5.31.0" hashes = [ - "h1:0WQSlLpN11nCeKu/k07BwcpypK0AfZDcbfkCxI/QbiE=", - "zh:0483ca802ddb0ae4f73144b4357ba72242c6e2641aeb460b1aa9a6f6965464b0", - "zh:274712214ebeb0c1269cbc468e5705bb5741dc45b05c05e9793ca97f22a1baa1", - "zh:3c6bd97a2ca809469ae38f6893348386c476cb3065b120b785353c1507401adf", - "zh:53dd41a9aed9860adbbeeb71a23e4f8195c656fd15a02c90fa2d302a5f577d8c", - "zh:65c639c547b97bc880fd83e65511c0f4bbfc91b63cada3b8c0d5776444221700", - "zh:a2769e19137ff480c1dd3e4f248e832df90fb6930a22c66264d9793895161714", - "zh:a5897a99332cc0071e46a71359b86a8e53ab09c1453e94cd7cf45a0b577ff590", - "zh:bdc2353642d16d8e2437a9015cd4216a1772be9736645cc17d1a197480e2b5b7", - "zh:cbeace1deae938f6c0aca3734e6088f3633ca09611aff701c15cb6d42f2b918a", - "zh:d33ca19012aabd98cc03fdeccd0bd5ce56e28f61a1dfbb2eea88e89487de7fb3", - "zh:d548b29a864b0687e85e8a993f208e25e3ecc40fcc5b671e1985754b32fdd658", + "h1:WwgMbMOhZblxZTdjHeJf9XB2/hcSHHmpuywLxuTWYw0=", + "zh:0cdb9c2083bf0902442384f7309367791e4640581652dda456f2d6d7abf0de8d", + "zh:2fe4884cb9642f48a5889f8dff8f5f511418a18537a9dfa77ada3bcdad391e4e", + "zh:36d8bdd72fe61d816d0049c179f495bc6f1e54d8d7b07c45b62e5e1696882a89", + "zh:539dd156e3ec608818eb21191697b230117437a58587cbd02ce533202a4dd520", + "zh:6a53f4b57ac4eb3479fc0d8b6e301ca3a27efae4c55d9f8bd24071b12a03361c", + "zh:6faeb8ff6792ca7af1c025255755ad764667a300291cc10cea0c615479488c87", + "zh:7d9423149b323f6d0df5b90c4d9029e5455c670aea2a7eb6fef4684ba7eb2e0b", + "zh:8235badd8a5d0993421cacf5ead48fac73d3b5a25c8a68599706a404b1f70730", + "zh:860b4f60842b2879c5128b7e386c8b49adeda9287fed12c5cd74861bb659bbcd", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b021fceaf9382c8fe3c6eb608c24d01dce3d11ba7e65bb443d51ca9b90e9b237", + "zh:b38b0bfc1c69e714e80cf1c9ea06e687ee86aa9f45694be28eb07adcebbe0489", + "zh:c972d155f6c01af9690a72adfb99cfc24ef5ef311ca92ce46b9b13c5c153f572", + "zh:e0dd29920ec84fdb6026acff44dcc1fb1a24a0caa093fa04cdbc713d384c651d", + "zh:e3127ebd2cb0374cd1808f911e6bffe2f4ac4d84317061381242353f3a7bc27d", ] } provider "registry.terraform.io/hashicorp/external" { - version = "2.2.0" + version = "2.3.2" hashes = [ - "h1:iU5OVMibHvIxbj2Dye1q3aYpjYXS3bKL9iZWZyh+xTg=", - "zh:094c3cfae140fbb70fb0e272b1df833b4d7467c6c819fbf59a3e8ac0922f95b6", - "zh:15c3906abbc1cd03a72afd02bda9caeeb5f6ca421292c32ddeb2acd7a3488669", - "zh:388c14bceeb1593bb16cadedc8f5ad7d41d398197db049dc0871bc847aa61083", - "zh:5696772136b6763faade0cc065fafc2bf06493021b943826be0144790fae514a", - "zh:6427c693b1b750644d5b633395e54617dc36ae717a531a5cde8cb0246b6593ca", - "zh:7196d9845eeffa3158f5e3067bf8b7ad489490aa26d29e2da1ad4c8924463469", + "h1:7F6FVQh7OcCgIH3YEJg1SJDSb1CU4qrCtGuI2EBHnL8=", + "zh:020bf652739ecd841d696e6c1b85ce7dd803e9177136df8fb03aa08b87365389", + "zh:0c7ea5a1cbf2e01a8627b8a84df69c93683f39fe947b288e958e72b9d12a827f", + "zh:25a68604c7d6aa736d6e99225051279eaac3a7cf4cab33b00ff7eae7096166f6", + "zh:34f46d82ca34604f6522de3b36eda19b7ad3be1e38947afc6ac31656eab58c8a", + "zh:6959f8f2f3de93e61e0abb90dbec41e28a66daec1607c46f43976bd6da50bcfd", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:8850d3ce9e5f5776b9349890ce4e2c4056defe16ed741dc845045942a6d9e025", - "zh:a2c6fc6cf087b35ebd6b6f20272ed32d4217ea9936c1dd630baa46d86718a455", - "zh:ac709be4ea5c9a6e1ab80e864d24cd9f8e6aaea29fb5dbe1de0897e2e86c3c17", - "zh:dcf806f044801fae5b21ae2754dc3c19c68e458d4584965752ce49be75305ff5", - "zh:f875b34be86c3439899828978638ef7e2d41a9e5e32397858a0c31daeaa1abc2", + "zh:a81e5d65a343da9caa6f1d17ae0aced9faecb36b4f8554bd445dbd4f8be21ab6", + "zh:b1d3f1557214d652c9120862ce27e9a7b61cb5aec5537a28240a5a37bf0b1413", + "zh:b71588d006471ae2d4a7eca2c51d69fd7c5dec9b088315599b794e2ad0cc5e90", + "zh:cfdaae4028b644dff3530c77b49d31f7e6f4c4e2a9e5c8ac6a88e383c80c9e9c", + "zh:dbde15154c2eb38a5f54d0e7646bc67510004179696f3cc2bc1d877cecacf83b", + "zh:fb681b363f83fb5f64dfa6afbf32d100d0facd2a766cf3493b8ddb0398e1b0f7", ] } diff --git a/infrastructure/applications/config.tf b/infrastructure/applications/config.tf index 1b915e53bc..cec39ac7bc 100644 --- a/infrastructure/applications/config.tf +++ b/infrastructure/applications/config.tf @@ -1,8 +1,8 @@ terraform { required_providers { aws = { - source = "hashicorp/aws" - version = "3.61.0" + source = "hashicorp/aws" + version = "5.31.0" configuration_aliases = [aws.us] } } diff --git a/infrastructure/applications/database/db.tf b/infrastructure/applications/database/db.tf index a122c0e703..0b4d8df7a8 100644 --- a/infrastructure/applications/database/db.tf +++ b/infrastructure/applications/database/db.tf @@ -19,7 +19,7 @@ resource "aws_db_instance" "database" { allow_major_version_upgrade = true engine_version = "14.7" instance_class = local.is_prod ? "db.t3.small" : "db.t3.micro" - name = "${local.normalized_workspace}backend" + db_name = "${local.normalized_workspace}backend" username = "root" password = module.common_secrets.value.database_password multi_az = "false" diff --git a/infrastructure/applications/database/proxy.tf b/infrastructure/applications/database/proxy.tf index dcdd59eb0b..ee005a7967 100644 --- a/infrastructure/applications/database/proxy.tf +++ b/infrastructure/applications/database/proxy.tf @@ -10,8 +10,11 @@ data "aws_vpc" "default" { } } -data "aws_subnet_ids" "private" { - vpc_id = data.aws_vpc.default.id +data "aws_subnets" "private" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } tags = { Type = "private" @@ -28,7 +31,7 @@ resource "aws_db_proxy" "proxy" { require_tls = false role_arn = aws_iam_role.proxy_role[0].arn vpc_security_group_ids = [data.aws_security_group.rds.id] - vpc_subnet_ids = data.aws_subnet_ids.private.ids + vpc_subnet_ids = data.aws_subnets.private.ids auth { auth_scheme = "SECRETS" diff --git a/infrastructure/applications/pretix/cache.tf b/infrastructure/applications/pretix/cache.tf index c067d653ee..503fad864b 100644 --- a/infrastructure/applications/pretix/cache.tf +++ b/infrastructure/applications/pretix/cache.tf @@ -1,7 +1,7 @@ resource "aws_elasticache_subnet_group" "default" { name = "${terraform.workspace}-pretix-redis-subnet" description = "${terraform.workspace} pretix redis subnet" - subnet_ids = [for subnet in data.aws_subnet_ids.private.ids : subnet] + subnet_ids = [for subnet in data.aws_subnets.private.ids : subnet] } resource "aws_elasticache_cluster" "cache" { diff --git a/infrastructure/applications/pretix/secrets.tf b/infrastructure/applications/pretix/secrets.tf index 4c4942323f..b18cdce2fc 100644 --- a/infrastructure/applications/pretix/secrets.tf +++ b/infrastructure/applications/pretix/secrets.tf @@ -1,5 +1,5 @@ module "secrets" { - source = "../../components/secrets" + source = "../../components/secrets" service = "pretix" } diff --git a/infrastructure/applications/pretix/vpc.tf b/infrastructure/applications/pretix/vpc.tf index e0c7335c7c..974ce32fd6 100644 --- a/infrastructure/applications/pretix/vpc.tf +++ b/infrastructure/applications/pretix/vpc.tf @@ -23,8 +23,11 @@ data "aws_subnet" "public" { } } -data "aws_subnet_ids" "private" { - vpc_id = data.aws_vpc.default.id +data "aws_subnets" "private" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } tags = { Type = "private" diff --git a/infrastructure/applications/pycon_backend/main.tf b/infrastructure/applications/pycon_backend/main.tf index 26bd21915a..e186ffb80a 100644 --- a/infrastructure/applications/pycon_backend/main.tf +++ b/infrastructure/applications/pycon_backend/main.tf @@ -1,9 +1,9 @@ locals { - is_prod = terraform.workspace == "production" - admin_domain = "admin" - full_admin_domain = local.is_prod ? "${local.admin_domain}.pycon.it" : "${terraform.workspace}-${local.admin_domain}.pycon.it" - db_connection = var.enable_proxy ? "postgres://${data.aws_db_instance.database.master_username}:${module.common_secrets.value.database_password}@${data.aws_db_proxy.proxy[0].endpoint}:${data.aws_db_instance.database.port}/pycon" : "postgres://${data.aws_db_instance.database.master_username}:${module.common_secrets.value.database_password}@${data.aws_db_instance.database.address}:${data.aws_db_instance.database.port}/pycon" - cdn_url = local.is_prod ? "cdn.pycon.it" : "${terraform.workspace}-cdn.pycon.it" + is_prod = terraform.workspace == "production" + admin_domain = "admin" + full_admin_domain = local.is_prod ? "${local.admin_domain}.pycon.it" : "${terraform.workspace}-${local.admin_domain}.pycon.it" + db_connection = var.enable_proxy ? "postgres://${data.aws_db_instance.database.master_username}:${module.common_secrets.value.database_password}@${data.aws_db_proxy.proxy[0].endpoint}:${data.aws_db_instance.database.port}/pycon" : "postgres://${data.aws_db_instance.database.master_username}:${module.common_secrets.value.database_password}@${data.aws_db_instance.database.address}:${data.aws_db_instance.database.port}/pycon" + cdn_url = local.is_prod ? "cdn.pycon.it" : "${terraform.workspace}-cdn.pycon.it" } data "aws_vpc" "default" { @@ -17,8 +17,11 @@ data "aws_iam_role" "lambda" { name = "pythonit-lambda-role" } -data "aws_subnet_ids" "private" { - vpc_id = data.aws_vpc.default.id +data "aws_subnets" "private" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } tags = { Type = "private" @@ -67,7 +70,7 @@ module "lambda" { application = local.application local_path = local.local_path role_arn = data.aws_iam_role.lambda.arn - subnet_ids = [for subnet in data.aws_subnet_ids.private.ids : subnet] + subnet_ids = [for subnet in data.aws_subnets.private.ids : subnet] security_group_ids = [data.aws_security_group.rds.id, data.aws_security_group.lambda.id] env_vars = { DATABASE_URL = local.db_connection @@ -109,6 +112,8 @@ module "lambda" { DEEPL_AUTH_KEY = module.secrets.value.deepl_auth_key FLODESK_API_KEY = module.secrets.value.flodesk_api_key FLODESK_SEGMENT_ID = module.secrets.value.flodesk_segment_id + CELERY_BROKER_URL = "redis://${data.aws_elasticache_cluster.redis.cache_nodes.0.address}/5" + CELERY_RESULT_BACKEND = "redis://${data.aws_elasticache_cluster.redis.cache_nodes.0.address}/6" } } diff --git a/infrastructure/applications/pycon_backend/providers.tf b/infrastructure/applications/pycon_backend/providers.tf index 6a64553a22..56c84f4a9b 100644 --- a/infrastructure/applications/pycon_backend/providers.tf +++ b/infrastructure/applications/pycon_backend/providers.tf @@ -1,8 +1,8 @@ terraform { required_providers { aws = { - source = "hashicorp/aws" - version = "3.61.0" + source = "hashicorp/aws" + version = "5.31.0" configuration_aliases = [aws.us] } } diff --git a/infrastructure/applications/pycon_backend/user_data.sh b/infrastructure/applications/pycon_backend/user_data.sh new file mode 100644 index 0000000000..2d37e49040 --- /dev/null +++ b/infrastructure/applications/pycon_backend/user_data.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -x + +# Config ECS agent +echo "ECS_CLUSTER=${ecs_cluster}" > /etc/ecs/ecs.config + +# Reclaim unused Docker disk space +cat << "EOF" > /usr/local/bin/claimspace.sh +#!/bin/bash +# Run fstrim on the host OS periodically to reclaim the unused container data blocks +docker ps -q | xargs docker inspect --format='{{ .State.Pid }}' | xargs -IZ sudo fstrim /proc/Z/root/ +exit $? +EOF + +chmod +x /usr/local/bin/claimspace.sh +echo "0 0 * * * root /usr/local/bin/claimspace.sh" > /etc/cron.d/claimspace diff --git a/infrastructure/applications/pycon_backend/worker.tf b/infrastructure/applications/pycon_backend/worker.tf new file mode 100644 index 0000000000..3436bfcf0c --- /dev/null +++ b/infrastructure/applications/pycon_backend/worker.tf @@ -0,0 +1,300 @@ +resource "aws_ecs_cluster" "worker" { + name = "pythonit-${terraform.workspace}-worker" +} + +data "aws_ami" "ecs" { + most_recent = true + + filter { + name = "name" + values = ["amzn-ami-*-amazon-ecs-optimized"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["amazon"] +} + +data "aws_subnet" "private_1a" { + vpc_id = data.aws_vpc.default.id + + filter { + name = "tag:Type" + values = ["private"] + } + + filter { + name = "tag:AZ" + values = ["eu-central-1a"] + } +} + + +data "template_file" "user_data" { + template = file("${path.module}/user_data.sh") + vars = { + ecs_cluster = aws_ecs_cluster.worker.name + } +} + + +resource "aws_instance" "instance" { + ami = "ami-05ff3e0fe4cf2c226" + instance_type = "t3a.micro" + subnet_id = data.aws_subnet.private_1a.id + availability_zone = "eu-central-1a" + vpc_security_group_ids = [ + data.aws_security_group.rds.id, + data.aws_security_group.lambda.id, + aws_security_group.instance.id + ] + source_dest_check = false + user_data = data.template_file.user_data.rendered + iam_instance_profile = aws_iam_instance_profile.worker.name + key_name = "pretix" + + tags = { + Name = "pythonit-${terraform.workspace}-worker" + } + + lifecycle { + prevent_destroy = true + } +} + +resource "aws_cloudwatch_log_group" "worker_logs" { + name = "/ecs/pythonit-${terraform.workspace}-worker" + retention_in_days = 7 +} + + +resource "aws_ecs_task_definition" "worker" { + family = "pythonit-${terraform.workspace}-worker" + container_definitions = jsonencode([ + { + name = "worker" + image = "${data.aws_ecr_repository.be_repo.repository_url}@${data.aws_ecr_image.be_image.image_digest}" + cpu = 2048 + memory = 951 + essential = true + entrypoint = [ + "/home/app/.venv/bin/python", + ] + + command = [ + "-m", "celery", "-A", "pycon", "worker", "-c", "2", + ] + + environment = [ + { + name = "DATABASE_URL", + value = local.db_connection + }, + { + name = "DEBUG", + value = "False" + }, + { + name = "SECRET_KEY", + value = module.secrets.value.secret_key + }, + { + name = "MAPBOX_PUBLIC_API_KEY", + value = module.secrets.value.mapbox_public_api_key + }, + { + name = "SENTRY_DSN", + value = module.secrets.value.sentry_dsn + }, + { + name = "VOLUNTEERS_PUSH_NOTIFICATIONS_IOS_ARN", + value = module.secrets.value.volunteers_push_notifications_ios_arn + }, + { + name = "VOLUNTEERS_PUSH_NOTIFICATIONS_ANDROID_ARN", + value = module.secrets.value.volunteers_push_notifications_android_arn + }, + { + name = "ALLOWED_HOSTS", + value = "*" + }, + { + name = "DJANGO_SETTINGS_MODULE", + value = "pycon.settings.prod" + }, + { + name = "ASSOCIATION_FRONTEND_URL", + value = "https://associazione.python.it" + }, + { + name = "AWS_MEDIA_BUCKET", + value = aws_s3_bucket.backend_media.id + }, + { + name = "AWS_REGION_NAME", + value = aws_s3_bucket.backend_media.region + }, + { + name = "SPEAKERS_EMAIL_ADDRESS", + value = module.secrets.value.speakers_email_address + }, + { + name = "EMAIL_BACKEND", + value = "django_ses.SESBackend" + }, + { + name = "PYTHONIT_EMAIL_BACKEND", + value = "pythonit_toolkit.emails.backends.ses.SESEmailBackend" + }, + { + name = "FRONTEND_URL", + value = "https://pycon.it" + }, + { + name = "PRETIX_API", + value = "https://tickets.pycon.it/api/v1/" + }, + { + name = "AWS_S3_CUSTOM_DOMAIN", + value = local.cdn_url + }, + { + name = "PRETIX_API_TOKEN", + value = module.common_secrets.value.pretix_api_token + }, + { + name = "PINPOINT_APPLICATION_ID", + value = module.secrets.value.pinpoint_application_id + }, + { + name = "FORCE_PYCON_HOST", + value = local.is_prod ? "true" : "false" + }, + { + name = "SQS_QUEUE_URL", + value = aws_sqs_queue.queue.id + }, + { + name = "MAILCHIMP_SECRET_KEY", + value = module.common_secrets.value.mailchimp_secret_key + }, + { + name = "MAILCHIMP_DC", + value = module.common_secrets.value.mailchimp_dc + }, + { + name = "MAILCHIMP_LIST_ID", + value = module.common_secrets.value.mailchimp_list_id + }, + { + name = "USER_ID_HASH_SALT", + value = module.secrets.value.userid_hash_salt + }, + { + name = "AZURE_STORAGE_ACCOUNT_NAME", + value = module.secrets.value.azure_storage_account_name + }, + { + name = "AZURE_STORAGE_ACCOUNT_KEY", + value = module.secrets.value.azure_storage_account_key + }, + { + name = "PLAIN_API", + value = "https://core-api.uk.plain.com/graphql/v1" + }, + { + name = "PLAIN_API_TOKEN", + value = module.secrets.value.plain_api_token + }, + { + name = "CACHE_URL", + value = local.is_prod ? "redis://${data.aws_elasticache_cluster.redis.cache_nodes.0.address}/8" : "locmemcache://snowflake" + }, + { + name = "TEMPORAL_ADDRESS", + value = var.deploy_temporal ? "${data.aws_instance.temporal_machine[0].private_ip}:7233" : "" + }, + { + name = "STRIPE_WEBHOOK_SIGNATURE_SECRET", + value = module.secrets.value.stripe_webhook_secret + }, + { + name = "STRIPE_SUBSCRIPTION_PRICE_ID", + value = module.secrets.value.stripe_membership_price_id + }, + { + name = "STRIPE_SECRET_API_KEY", + value = module.secrets.value.stripe_secret_api_key + }, + { + name = "PRETIX_WEBHOOK_SECRET", + value = module.secrets.value.pretix_webhook_secret + }, + { + name = "DEEPL_AUTH_KEY", + value = module.secrets.value.deepl_auth_key + }, + { + name = "FLODESK_API_KEY", + value = module.secrets.value.flodesk_api_key + }, + { + name = "FLODESK_SEGMENT_ID", + value = module.secrets.value.flodesk_segment_id + }, + { + name = "CELERY_BROKER_URL", + value = "redis://${data.aws_elasticache_cluster.redis.cache_nodes.0.address}/5" + }, + { + name = "CELERY_RESULT_BACKEND", + value = "redis://${data.aws_elasticache_cluster.redis.cache_nodes.0.address}/6" + }, + ] + + mountPoints = [] + systemControls = [ + { + "namespace" : "net.core.somaxconn", + "value" : "4096" + } + ] + + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.worker_logs.name + "awslogs-region" = "eu-central-1" + "awslogs-stream-prefix" = "ecs" + } + } + + healthCheck = { + retries = 3 + command = [ + "CMD-SHELL", + "echo 1" + ] + timeout = 3 + interval = 10 + } + + stopTimeout = 300 + }, + ]) + + requires_compatibilities = [] + tags = {} +} + +resource "aws_ecs_service" "worker" { + name = "pythonit-${terraform.workspace}-worker" + cluster = aws_ecs_cluster.worker.id + task_definition = aws_ecs_task_definition.worker.arn + desired_count = 1 + deployment_minimum_healthy_percent = 0 + deployment_maximum_percent = 100 +} diff --git a/infrastructure/applications/pycon_backend/worker_repo.tf b/infrastructure/applications/pycon_backend/worker_repo.tf new file mode 100644 index 0000000000..29e093bf69 --- /dev/null +++ b/infrastructure/applications/pycon_backend/worker_repo.tf @@ -0,0 +1,15 @@ +data "aws_ecr_repository" "be_repo" { + name = "pythonit/pycon-backend" +} + +data "aws_ecr_image" "be_image" { + repository_name = data.aws_ecr_repository.be_repo.name + image_tag = data.external.githash.result.githash +} + +data "aws_caller_identity" "current" {} + +data "external" "githash" { + program = ["python", abspath("${path.module}/../pretix/githash.py")] + working_dir = abspath("${path.root}/../../backend") +} diff --git a/infrastructure/applications/pycon_backend/worker_role.tf b/infrastructure/applications/pycon_backend/worker_role.tf new file mode 100644 index 0000000000..c7762991d8 --- /dev/null +++ b/infrastructure/applications/pycon_backend/worker_role.tf @@ -0,0 +1,65 @@ +resource "aws_iam_role" "worker" { + name = "pythonit-${terraform.workspace}-worker" + + assume_role_policy = <