diff --git a/.env b/.env deleted file mode 100644 index ff24258b3..000000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -# Environment variables used by Docker (specifically for docker-compose.yml) -DB_NAME=cf_brc_db -COLDFRONT_PORT=8880 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 581856dee..588c3679d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dist build *._* *.DS_Store +.env *.swp env/ venv/ diff --git a/Dockerfile.email b/Dockerfile.email new file mode 100644 index 000000000..879426b26 --- /dev/null +++ b/Dockerfile.email @@ -0,0 +1,5 @@ +FROM python:3 + +LABEL description="coldfront email" + +RUN apt update && apt install -y sudo net-tools diff --git a/README.md b/README.md index d7fa75d44..9bce3b2e6 100644 --- a/README.md +++ b/README.md @@ -213,14 +213,42 @@ multiple files or directories to omit. - Open `htmlcov/index.html` in a browser to view which lines of code were covered by the tests and which were not. -## Docker - Quick Install (Recommend) -1. Generate configuration (`dev_settings.py`): have Python with the `jinja2` and `pyyaml` libraries installed, and then run `bootstrap/development/gen_config.sh` -2. Build Images: In the base directory, run `docker build . -t coldfront` and `docker build . -f Dockerfile.db -t coldfront_db`. `--build-arg PORTAL=mylrc` can be added to the build command to build for mylrc. -3. If needed, modify `.env` to customize the web server port and the database name (e.g from `cf_mybrc_db` to `cf_mylrc_db`) -4. To run: In the base directory, run `docker compose up` -5. To enter the coldfront container (similar to `vagrant ssh`): run `docker exec -it coldfront-coldfront-1 bash` -6. To load a database backup: run `bootstrap/development/docker_load_database_backup.sh ${DB_NAME} ${PATH_TO_DUMP}` -7. To start from scratch (delete volumes): In the base directory, run `docker compose down --volumes` +## Docker - Quick Install (Recommended) +1. Create and customize `main.yml`, as described above. + - In the Vagrant VM setup and in production, this file is used to configure Ansible. In particular, variables in this file are used to generate a deployment-specific Django settings file. + - However, Ansible is not used in the Docker setup. Variables are manually parsed to generate the configuration file (`dev_settings.py`). +2. Manually generate a deployment-specific Django settings file from `main.yml`. + ```bash + # This can be done in a Python virtual environment. + pip install jinja2 pyyaml + sh bootstrap/development/gen_config.sh + ``` +3. Build images. In the base directory, run: + ```bash + docker build . -t coldfront + docker build . -f Dockerfile.db -t coldfront_db + docker build . -f Dockerfile.email -t coldfront_email + ``` + Note: The above commands build images meant for a MyBRC instance. To build MyLRC images, include `--build-arg PORTAL=mylrc`. +4. Configure environment variables to be injected into containers by creating a `.env` file in the root directory (ignored by Git) or by setting them manually. + - `DB_NAME=cf_brc_db`: The name of the database can be customized (e.g., for a MyLRC instance, change it to `cf_lrc_db`). + - `COLDFRONT_PORT=8880`: The port can be customized so that multiple instances can be run without conflicting. +5. Bring up the stack. In the root directory, run: + ```bash + docker compose up + ``` +6. To SSH into the `coldfront` container, run: + ```bash + docker exec -it coldfront-coldfront-1 bash + ``` +7. To load a database backup, run: + ```bash + sh bootstrap/development/docker_load_database_backup.sh ${DB_NAME} ${PATH_TO_DUMP} + ``` +8. To start from scratch, deleting created volumes, run: + ```bash + docker compose down --volumes + ``` ## Local Machine - Quick Install (Not Recommended) diff --git a/bootstrap/ansible/main.copyme b/bootstrap/ansible/main.copyme index c47b1599c..423213ea4 100644 --- a/bootstrap/ansible/main.copyme +++ b/bootstrap/ansible/main.copyme @@ -174,6 +174,7 @@ sentry_dsn: "" # debug_toolbar_ips: [] # # Email settings. +# email_host: localhost # email_port: 25 # # TODO: Set these addresses to yours. # from_email: brc-hpc-help@berkeley.edu @@ -229,6 +230,7 @@ sentry_dsn: "" # debug_toolbar_ips: [] # # Email settings. +# email_host: localhost # email_port: 25 # # TODO: Set these addresses to yours. # from_email: brc-hpc-help@berkeley.edu @@ -281,6 +283,7 @@ sentry_dsn: "" # debug_toolbar_ips: ['10.0.2.2'] # 10.0.2.2 is the vagrant host. # # Email settings. +# email_host: localhost # email_port: 1025 # # TODO: Set these addresses to yours. # from_email: you@email.com diff --git a/bootstrap/ansible/settings_template.tmpl b/bootstrap/ansible/settings_template.tmpl index 279b0b7fc..73cdae2d9 100644 --- a/bootstrap/ansible/settings_template.tmpl +++ b/bootstrap/ansible/settings_template.tmpl @@ -21,6 +21,7 @@ CENTER_BASE_URL = '{{ full_host_path }}' CENTER_HELP_URL = CENTER_BASE_URL + '/help' CENTER_PROJECT_RENEWAL_HELP_URL = CENTER_BASE_URL + '/help' +EMAIL_HOST = '{{ email_host}}' EMAIL_PORT = {{ email_port }} EMAIL_SUBJECT_PREFIX = '{{ email_subject_prefix }}' # A list of admin email addresses to be notified about new requests and other diff --git a/bootstrap/development/docker_load_database_backup.sh b/bootstrap/development/docker_load_database_backup.sh index 6ea06111f..130c274bf 100644 --- a/bootstrap/development/docker_load_database_backup.sh +++ b/bootstrap/development/docker_load_database_backup.sh @@ -1,4 +1,4 @@ #!/bin/bash # $1 = database name # $2 = dump file -docker exec -i coldfront-db-1 pg_restore --verbose --clean -U admin -d $1 < $2 +docker exec -i coldfront-db-1 pg_restore --verbose --clean -U admin -d $1<$2 diff --git a/bootstrap/development/gen_config.py b/bootstrap/development/gen_config.py new file mode 100644 index 000000000..0325c4fc4 --- /dev/null +++ b/bootstrap/development/gen_config.py @@ -0,0 +1,14 @@ +from jinja2 import Environment +from jinja2 import FileSystemLoader +import yaml + + +env = Environment(loader=FileSystemLoader('bootstrap/ansible/')) +env.filters['bool'] = lambda x: str(x).lower() in ['true', 'yes', 'on', '1'] +options = yaml.safe_load(open('main.yml').read()) +options.update({ + 'db_host': 'db', + 'email_host': 'email', + 'redis_host': 'redis', +}) +print(env.get_template('settings_template.tmpl').render(options)) diff --git a/bootstrap/development/gen_config.sh b/bootstrap/development/gen_config.sh index 7382379ed..25bd98740 100644 --- a/bootstrap/development/gen_config.sh +++ b/bootstrap/development/gen_config.sh @@ -1,14 +1,4 @@ #!/usr/bin/env bash -cp coldfront/config/local_settings.py.sample \ - coldfront/config/local_settings.py -cp coldfront/config/local_strings.py.sample \ - coldfront/config/local_strings.py -python -c \ -"from jinja2 import Template, Environment, FileSystemLoader; \ -import yaml; \ -env = Environment(loader=FileSystemLoader('bootstrap/ansible/')); \ -env.filters['bool'] = lambda x: str(x).lower() in ['true', 'yes', 'on', '1']; \ -options = yaml.safe_load(open('main.yml').read()); \ -options.update({'redis_host': 'redis', 'db_host': 'db'}); \ -print(env.get_template('settings_template.tmpl').render(options))" \ - > coldfront/config/dev_settings.py +cp coldfront/config/local_settings.py.sample coldfront/config/local_settings.py +cp coldfront/config/local_strings.py.sample coldfront/config/local_strings.py +python bootstrap/development/gen_config.py > coldfront/config/dev_settings.py diff --git a/coldfront/core/allocation/management/commands/approve_renewal_requests_for_allocation_period.py b/coldfront/core/allocation/management/commands/approve_renewal_requests_for_allocation_period.py index 1d25c90e7..c7a24ac2e 100644 --- a/coldfront/core/allocation/management/commands/approve_renewal_requests_for_allocation_period.py +++ b/coldfront/core/allocation/management/commands/approve_renewal_requests_for_allocation_period.py @@ -13,6 +13,8 @@ from coldfront.core.utils.common import display_time_zone_current_date from coldfront.core.utils.common import display_time_zone_date_to_utc_datetime from coldfront.core.utils.common import utc_now_offset_aware +from coldfront.core.utils.email.email_strategy import DropEmailStrategy +from coldfront.core.utils.email.email_strategy import SendEmailStrategy """An admin command that approves AllocationRenewalRequests for a @@ -34,12 +36,18 @@ def add_arguments(self, parser): 'allocation_period_id', help='The ID of the AllocationPeriod.', type=int) + parser.add_argument( + '--skip_emails', + action='store_true', + default=False, + help='Skip sending notification emails to requesters and PIs.') add_argparse_dry_run_argument(parser) def handle(self, *args, **options): """Approve eligible requests if the AllocationPeriod is valid and eligible.""" dry_run = options['dry_run'] + skip_emails = options['skip_emails'] allocation_period_id = options['allocation_period_id'] try: @@ -83,8 +91,11 @@ def handle(self, *args, **options): message_template = ( f'{{0}} AllocationRenewalRequest {{1}} for PI {{2}}, scheduling ' - f'{{3}} to be granted to {{4}} on {allocation_period_start_date}, ' - f'and emailing the requester and/or PI.') + f'{{3}} to be granted to {{4}} on {allocation_period_start_date}') + if skip_emails: + message_template += '.' + else: + message_template += ', and emailing the requester and/or PI.' for request in requests: num_service_units = num_service_units_by_allowance_name[ request.computing_allowance.name] @@ -99,7 +110,8 @@ def handle(self, *args, **options): try: self.update_request_state(request) request.refresh_from_db() - self.approve_request(request, num_service_units) + self.approve_request( + request, num_service_units, skip_emails=skip_emails) except Exception as e: message = ( f'Failed to approve AllocationRenewalRequest ' @@ -123,9 +135,11 @@ def update_request_state(request): request.save() @staticmethod - def approve_request(request, num_service_units): - """Instantiate and run tne approval runner for the given request - and number of service units, sending emails.""" + def approve_request(request, num_service_units, skip_emails=False): + """Instantiate and run the approval runner for the given request + and number of service units. Optionally skip sending email.""" + email_strategy = ( + DropEmailStrategy() if skip_emails else SendEmailStrategy()) approval_runner = AllocationRenewalApprovalRunner( - request, num_service_units) + request, num_service_units, email_strategy=email_strategy) approval_runner.run() diff --git a/coldfront/core/allocation/management/commands/create_allocation_periods.py b/coldfront/core/allocation/management/commands/create_allocation_periods.py index ff1a13432..53481d377 100644 --- a/coldfront/core/allocation/management/commands/create_allocation_periods.py +++ b/coldfront/core/allocation/management/commands/create_allocation_periods.py @@ -4,7 +4,9 @@ from django.core.management.base import CommandError from flags.state import flag_enabled +import json import logging +import os """An admin command that creates AllocationPeriods.""" @@ -56,6 +58,9 @@ def handle(self, *args, **options): else: prev_start_date = allocation_period.start_date prev_end_date = allocation_period.end_date + if start_date == prev_start_date and end_date == prev_end_date: + # No update needed. + continue message_template = ( f'{{0}} AllocationPeriod {allocation_period.pk} with ' f'name "{name}" from ({prev_start_date}, {prev_end_date}) ' @@ -94,126 +99,17 @@ def get_allocation_periods(): based on the current deployment.""" periods = [] + relative_json_path = None if flag_enabled('BRC_ONLY'): - periods.extend( - [ - { - "name": "Allowance Year 2020 - 2021", - "start_date": "2020-06-01", - "end_date": "2021-05-31" - }, - { - "name": "Allowance Year 2021 - 2022", - "start_date": "2021-06-01", - "end_date": "2022-05-31" - }, - { - "name": "Allowance Year 2022 - 2023", - "start_date": "2022-06-01", - "end_date": "2023-05-31" - }, - { - "name": "Allowance Year 2023 - 2024", - "start_date": "2023-06-01", - "end_date": "2024-05-31" - }, - { - "name": "Fall Semester 2021", - "start_date": "2021-08-18", - "end_date": "2021-12-17" - }, - { - "name": "Spring Semester 2022", - "start_date": "2022-01-11", - "end_date": "2022-05-13" - }, - { - "name": "Summer Sessions 2022 - Session A", - "start_date": "2022-05-23", - "end_date": "2022-07-01" - }, - { - "name": "Summer Sessions 2022 - Session B", - "start_date": "2022-06-06", - "end_date": "2022-08-12" - }, - { - "name": "Summer Sessions 2022 - Session C", - "start_date": "2022-06-21", - "end_date": "2022-08-12" - }, - { - "name": "Summer Sessions 2022 - Session D", - "start_date": "2022-07-05", - "end_date": "2022-08-12" - }, - { - "name": "Summer Sessions 2022 - Session E", - "start_date": "2022-07-25", - "end_date": "2022-08-12" - }, - { - "name": "Summer Sessions 2022 - Session F", - "start_date": "2022-07-05", - "end_date": "2022-07-22" - }, - { - "name": "Fall Semester 2022", - "start_date": "2022-08-17", - "end_date": "2022-12-16" - }, - { - "name": "Spring Semester 2023", - "start_date": "2023-01-10", - "end_date": "2023-05-12" - }, - { - "name": "Summer Sessions 2023 - Session A", - "start_date": "2023-05-22", - "end_date": "2023-06-30" - }, - { - "name": "Summer Sessions 2023 - Session B", - "start_date": "2023-06-05", - "end_date": "2023-08-11" - }, - { - "name": "Summer Sessions 2023 - Session C", - "start_date": "2023-06-20", - "end_date": "2023-08-11" - }, - { - "name": "Summer Sessions 2023 - Session D", - "start_date": "2023-07-03", - "end_date": "2023-08-11" - }, - { - "name": "Summer Sessions 2023 - Session E", - "start_date": "2023-07-24", - "end_date": "2023-08-11" - }, - { - "name": "Summer Sessions 2023 - Session F", - "start_date": "2023-07-03", - "end_date": "2023-07-21" - } - ] - ) - + relative_json_path = 'data/brc_allocation_periods.json' if flag_enabled('LRC_ONLY'): - periods.extend( - [ - { - "name": "Allowance Year 2021 - 2022", - "start_date": "2021-10-01", - "end_date": "2022-09-30" - }, - { - "name": "Allowance Year 2022 - 2023", - "start_date": "2022-10-01", - "end_date": "2023-09-30" - } - ] - ) + relative_json_path = 'data/lrc_allocation_periods.json' + + if relative_json_path is not None: + absolute_json_path = os.path.join( + os.path.dirname(__file__), relative_json_path) + with open(absolute_json_path, 'r') as f: + period_list = json.load(f) + periods.extend(period_list) return periods diff --git a/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json b/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json new file mode 100644 index 000000000..ad4af549f --- /dev/null +++ b/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json @@ -0,0 +1,147 @@ +[ + { + "name": "Allowance Year 2020 - 2021", + "start_date": "2020-06-01", + "end_date": "2021-05-31" + }, + { + "name": "Allowance Year 2021 - 2022", + "start_date": "2021-06-01", + "end_date": "2022-05-31" + }, + { + "name": "Allowance Year 2022 - 2023", + "start_date": "2022-06-01", + "end_date": "2023-05-31" + }, + { + "name": "Allowance Year 2023 - 2024", + "start_date": "2023-06-01", + "end_date": "2024-05-31" + }, + { + "name": "Allowance Year 2024 - 2025", + "start_date": "2024-06-01", + "end_date": "2025-05-31" + }, + { + "name": "Fall Semester 2021", + "start_date": "2021-08-18", + "end_date": "2021-12-17" + }, + { + "name": "Spring Semester 2022", + "start_date": "2022-01-11", + "end_date": "2022-05-13" + }, + { + "name": "Summer Sessions 2022 - Session A", + "start_date": "2022-05-23", + "end_date": "2022-07-01" + }, + { + "name": "Summer Sessions 2022 - Session B", + "start_date": "2022-06-06", + "end_date": "2022-08-12" + }, + { + "name": "Summer Sessions 2022 - Session C", + "start_date": "2022-06-21", + "end_date": "2022-08-12" + }, + { + "name": "Summer Sessions 2022 - Session D", + "start_date": "2022-07-05", + "end_date": "2022-08-12" + }, + { + "name": "Summer Sessions 2022 - Session E", + "start_date": "2022-07-25", + "end_date": "2022-08-12" + }, + { + "name": "Summer Sessions 2022 - Session F", + "start_date": "2022-07-05", + "end_date": "2022-07-22" + }, + { + "name": "Fall Semester 2022", + "start_date": "2022-08-17", + "end_date": "2022-12-16" + }, + { + "name": "Spring Semester 2023", + "start_date": "2023-01-10", + "end_date": "2023-05-12" + }, + { + "name": "Summer Sessions 2023 - Session A", + "start_date": "2023-05-22", + "end_date": "2023-06-30" + }, + { + "name": "Summer Sessions 2023 - Session B", + "start_date": "2023-06-05", + "end_date": "2023-08-11" + }, + { + "name": "Summer Sessions 2023 - Session C", + "start_date": "2023-06-20", + "end_date": "2023-08-11" + }, + { + "name": "Summer Sessions 2023 - Session D", + "start_date": "2023-07-03", + "end_date": "2023-08-11" + }, + { + "name": "Summer Sessions 2023 - Session E", + "start_date": "2023-07-24", + "end_date": "2023-08-11" + }, + { + "name": "Summer Sessions 2023 - Session F", + "start_date": "2023-07-03", + "end_date": "2023-07-21" + }, + { + "name": "Fall Semester 2023", + "start_date": "2023-08-16", + "end_date": "2023-12-15" + }, + { + "name": "Spring Semester 2024", + "start_date": "2024-01-09", + "end_date": "2024-05-10" + }, + { + "name": "Summer Sessions 2024 - Session A", + "start_date": "2024-05-20", + "end_date": "2024-06-28" + }, + { + "name": "Summer Sessions 2024 - Session B", + "start_date": "2024-06-03", + "end_date": "2024-08-09" + }, + { + "name": "Summer Sessions 2024 - Session C", + "start_date": "2024-06-17", + "end_date": "2024-08-09" + }, + { + "name": "Summer Sessions 2024 - Session D", + "start_date": "2024-07-01", + "end_date": "2024-08-09" + }, + { + "name": "Summer Sessions 2024 - Session E", + "start_date": "2024-07-22", + "end_date": "2024-08-09" + }, + { + "name": "Summer Sessions 2024 - Session F", + "start_date": "2024-07-01", + "end_date": "2024-07-19" + } +] \ No newline at end of file diff --git a/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json b/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json new file mode 100644 index 000000000..00c1609b9 --- /dev/null +++ b/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json @@ -0,0 +1,22 @@ +[ + { + "name": "Allowance Year 2021 - 2022", + "start_date": "2021-10-01", + "end_date": "2022-09-30" + }, + { + "name": "Allowance Year 2022 - 2023", + "start_date": "2022-10-01", + "end_date": "2023-09-30" + }, + { + "name": "Allowance Year 2023 - 2024", + "start_date": "2023-10-01", + "end_date": "2024-09-30" + }, + { + "name": "Allowance Year 2024 - 2025", + "start_date": "2024-10-01", + "end_date": "2025-09-30" + } +] \ No newline at end of file diff --git a/coldfront/core/allocation/management/commands/start_allocation_period.py b/coldfront/core/allocation/management/commands/start_allocation_period.py index 387c255c9..890231f17 100644 --- a/coldfront/core/allocation/management/commands/start_allocation_period.py +++ b/coldfront/core/allocation/management/commands/start_allocation_period.py @@ -13,6 +13,8 @@ from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface from coldfront.core.utils.common import add_argparse_dry_run_argument from coldfront.core.utils.common import display_time_zone_current_date +from coldfront.core.utils.email.email_strategy import DropEmailStrategy +from coldfront.core.utils.email.email_strategy import SendEmailStrategy from decimal import Decimal @@ -52,6 +54,11 @@ def add_arguments(self, parser): 'Do not deactivate Projects prior to processing requests. ' 'This is useful in case any outstanding requests need to be ' 'processed by re-running the command.')) + parser.add_argument( + '--skip_emails', + action='store_true', + default=False, + help='Skip sending notification emails to requesters and PIs.') add_argparse_dry_run_argument(parser) def handle(self, *args, **options): @@ -66,6 +73,7 @@ def handle(self, *args, **options): f'AllocationPeriod {allocation_period_id} does not exist.') skip_deactivations = options['skip_deactivations'] + skip_emails = options['skip_emails'] dry_run = options['dry_run'] if not dry_run: @@ -76,7 +84,7 @@ def handle(self, *args, **options): f'{allocation_period.end_date}) is not current.') self.handle_allocation_period( - allocation_period, skip_deactivations, dry_run) + allocation_period, skip_deactivations, skip_emails, dry_run) def deactivate_projects(self, projects, dry_run): """Deactivate the given queryset of Projects. Return the number @@ -166,10 +174,11 @@ def get_deactivation_eligible_projects(self, allocation_period, return Project.objects.filter(pk__in=expired_project_pks) def handle_allocation_period(self, allocation_period, skip_deactivations, - dry_run): + skip_emails, dry_run): """Optionally deactivate eligible projects associated with the given period. Then, process all requests for new projects and allocation renewals that are scheduled for the period. + Optionally skip emails during processing. If any deactivations fail, do not proceed with processing requests. @@ -196,8 +205,10 @@ def handle_allocation_period(self, allocation_period, skip_deactivations, # New project requests should be processed prior to renewal requests, # since a renewal request may depend on a new project request. - self.process_new_project_requests(allocation_period, dry_run) - self.process_allocation_renewal_requests(allocation_period, dry_run) + self.process_new_project_requests( + allocation_period, skip_emails, dry_run) + self.process_allocation_renewal_requests( + allocation_period, skip_emails, dry_run) @staticmethod def is_allocation_period_current(allocation_period): @@ -207,32 +218,38 @@ def is_allocation_period_current(allocation_period): display_time_zone_current_date() <= allocation_period.end_date) - def process_allocation_renewal_requests(self, allocation_period, dry_run): + def process_allocation_renewal_requests(self, allocation_period, + skip_emails, dry_run): """Process the "Approved" AllocationRenewalRequests for the - given AllocationPeriod and allowance. Optionally display updates - instead of performing them.""" + given AllocationPeriod and allowance. Optionally skip emails. + Optionally display updates instead of performing them.""" model = AllocationRenewalRequest runner_class = AllocationRenewalProcessingRunner eligible_requests = model.objects.filter( allocation_period=allocation_period, status__name='Approved') - self.process_requests(model, runner_class, eligible_requests, dry_run) + self.process_requests( + model, runner_class, eligible_requests, skip_emails, dry_run) - def process_new_project_requests(self, allocation_period, dry_run): + def process_new_project_requests(self, allocation_period, skip_emails, + dry_run): """Process the "Approved - Scheduled" SavioProjectAllocationRequests for the given AllocationPeriod. - Optionally display updates instead of performing them.""" + Optionally skip emails. Optionally display updates instead of + performing them.""" model = SavioProjectAllocationRequest runner_class = SavioProjectProcessingRunner eligible_requests = model.objects.filter( allocation_period=allocation_period, status__name='Approved - Scheduled') - self.process_requests(model, runner_class, eligible_requests, dry_run) + self.process_requests( + model, runner_class, eligible_requests, skip_emails, dry_run) - def process_requests(self, model, runner_class, requests, dry_run): + def process_requests(self, model, runner_class, requests, skip_emails, + dry_run): """Given a request model, a runner class for processing instances of that model, and a queryset of instances to process, - run the runner on each instance. Optionally display updates - instead of performing them.""" + run the runner on each instance. Optionally skip sending emails. + Optionally display updates instead of performing them.""" model_name = model.__name__ num_successes, num_failures = 0, 0 @@ -265,7 +282,19 @@ def process_requests(self, model, runner_class, requests, dry_run): continue try: - runner = runner_class(request, num_service_units) + email_strategy = ( + DropEmailStrategy() if skip_emails else SendEmailStrategy()) + except Exception as e: + num_failures = num_failures + 1 + message = ( + f'Failed to instantiate email strategy for {model_name} ' + f'{request.pk}: {e}') + self.stderr.write(self.style.ERROR(message)) + continue + + try: + runner = runner_class( + request, num_service_units, email_strategy=email_strategy) except Exception as e: num_failures = num_failures + 1 message = ( diff --git a/docker-compose.yml b/docker-compose.yml index 60c6d84d8..9ba68bf33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,23 @@ services: retries: 5 restart: unless-stopped + email: + image: python_email:latest + build: + context: . + dockerfile: Dockerfile.email + restart: always + environment: + - PYTHONUNBUFFERED=1 + ports: + - "1025:1025" + healthcheck: + test: ["CMD","netstat","-l","|","grep","1025"] + interval: 10s + timeout: 5s + retries: 5 + command: python -m smtpd -d -n -c DebuggingServer 0.0.0.0:1025 + coldfront: image: coldfront:latest build: @@ -50,6 +67,8 @@ services: condition: service_healthy redis: condition: service_healthy + email: + condition: service_healthy restart: unless-stopped volumes: @@ -57,4 +76,3 @@ volumes: external: false redis_data: external: false -