diff --git a/.github/workflow-readme.md b/.github/workflow-readme.md index 3e75fad2..8980d4af 100644 --- a/.github/workflow-readme.md +++ b/.github/workflow-readme.md @@ -30,7 +30,7 @@ The "Build PR on Dev" pipeline will be triggered when it identified pull request - Squash merge the tracking pull request to main - Create the release on GitHub from main branch -- Create the new release branch from main branch (this is done automatically by pipeline create-release.yaml) +- Create the new release branch from main branch - Change the new release branch as the default branch in the repo and update the branch protection rules https://github.com/bcgov/itvr/settings/branches - Update frontend/package.json - version diff --git a/django/Dockerfile b/django/Dockerfile index d80005fe..94c81547 100644 --- a/django/Dockerfile +++ b/django/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 python:3.9.1 +FROM python:3.9.1 ENV PYTHONUNBUFFERED=1 diff --git a/django/api/apps.py b/django/api/apps.py index f844f05b..0c5c9e8a 100644 --- a/django/api/apps.py +++ b/django/api/apps.py @@ -16,6 +16,8 @@ def ready(self): schedule_get_ncda_redeemed_rebates, schedule_expire_expired_applications, schedule_send_expiry_emails, + schedule_send_to_cra, + schedule_retrieve_from_cra, ) if settings.RUN_JOBS and "qcluster" in sys.argv: @@ -23,6 +25,8 @@ def ready(self): schedule_get_ncda_redeemed_rebates() schedule_expire_expired_applications() schedule_send_expiry_emails() + schedule_send_to_cra() + schedule_retrieve_from_cra() class ITVRAdminConfig(AdminConfig): diff --git a/django/api/migrations/0023_crafiletracker.py b/django/api/migrations/0023_crafiletracker.py new file mode 100644 index 00000000..da6907bd --- /dev/null +++ b/django/api/migrations/0023_crafiletracker.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0.9 on 2025-09-05 03:31 + +from django.db import migrations, models +import django_extensions.db.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0022_legacygoelectricrebateapplication'), + ] + + operations = [ + migrations.CreateModel( + name='CraFileTracker', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), + ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), + ('sequence_number', models.IntegerField()), + ], + options={ + 'db_table': 'cra_file_tracker', + }, + ), + ] diff --git a/django/api/models/__init__.py b/django/api/models/__init__.py index 0dacf459..86f48421 100644 --- a/django/api/models/__init__.py +++ b/django/api/models/__init__.py @@ -4,3 +4,4 @@ from . import go_electric_rebate_application from . import household_member from . import go_electric_rebate +from . import cra_file_tracker diff --git a/django/api/models/cra_file_tracker.py b/django/api/models/cra_file_tracker.py new file mode 100644 index 00000000..351f37aa --- /dev/null +++ b/django/api/models/cra_file_tracker.py @@ -0,0 +1,11 @@ +from django_extensions.db.models import TimeStampedModel +from django.db.models import ( + IntegerField, +) + + +class CraFileTracker(TimeStampedModel): + sequence_number = IntegerField() + + class Meta: + db_table = "cra_file_tracker" diff --git a/django/api/scheduled_jobs.py b/django/api/scheduled_jobs.py index 4c4d5498..9a2508e3 100644 --- a/django/api/scheduled_jobs.py +++ b/django/api/scheduled_jobs.py @@ -59,3 +59,29 @@ def schedule_send_expiry_emails(): ) except IntegrityError: pass + + +def schedule_send_to_cra(): + try: + schedule( + "api.tasks.send_to_cra", + name="send_to_cra", + schedule_type="C", + cron="00 * * * *", + q_options={"timeout": 900, "ack_failure": True}, + ) + except IntegrityError: + pass + + +def schedule_retrieve_from_cra(): + try: + schedule( + "api.tasks.retrieve_from_cra", + name="retrieve_from_cra", + schedule_type="C", + cron="30 * * * *", + q_options={"timeout": 900, "ack_failure": True}, + ) + except IntegrityError: + pass diff --git a/django/api/services/cra.py b/django/api/services/cra.py index c38aaf0d..1bd8b188 100644 --- a/django/api/services/cra.py +++ b/django/api/services/cra.py @@ -1,7 +1,10 @@ import requests import json +import io from unidecode import unidecode from django.conf import settings +from api.services.minio import get_minio_object +import paramiko def read(file): @@ -42,9 +45,8 @@ def read(file): return results -def write( - data, today="20220516", program_code="BCVR", cra_env="A", cra_sequence="00001" -): +def write(data, today="20220516", program_code="BCVR", cra_env="A", cra_sequence=1): + sequence_number = (str(cra_sequence)).rjust(5, "0") file = "" # Number of records to write. @@ -58,7 +60,7 @@ def write( file += today # file += " " # Blank space - file += program_code + cra_env + cra_sequence + file += program_code + cra_env + sequence_number file += " " * 99 # Blank space @@ -82,7 +84,7 @@ def write( file += today # Request date file += " " # Blank space - file += program_code + cra_env + cra_sequence + file += program_code + cra_env + sequence_number file += " " * 6 # Blank space @@ -124,3 +126,40 @@ def encrypt(to_encrypt): ) response.raise_for_status() return response.content + + +def get_to_cra_filename(program_code="BCVR", cra_env="A", cra_sequence=1, directory=""): + filename = "{directory}/TO.{cra_env}TO#@@00.R7005.IN.{program_code}.{cra_env}{cra_sequence:05}.p7m".format( + directory=directory, + cra_env=cra_env, + cra_sequence=cra_sequence, + program_code=program_code, + ) + return filename + + +def get_from_cra_filename( + program_code="BCVR", cra_env="A", cra_sequence=1, directory="" +): + filename = "{directory}/{cra_env}{program_code}{cra_sequence:05}.p7m".format( + directory=directory, + cra_env=cra_env, + cra_sequence=cra_sequence, + program_code=program_code, + ) + return filename + + +def get_ssh_client(): + pkey_file = get_minio_object(settings.SSH_PKEY_FILENAME) + pkey = paramiko.RSAKey.from_private_key(io.StringIO(pkey_file.data.decode("utf-8"))) + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy) + client.connect( + settings.CRA_SFTP_HOST, + port=int(settings.CRA_SFTP_PORT), + username=settings.CRA_SFTP_USERNAME, + pkey=pkey, + disabled_algorithms={"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"]}, + ) + return client diff --git a/django/api/services/minio.py b/django/api/services/minio.py new file mode 100644 index 00000000..c044d8d8 --- /dev/null +++ b/django/api/services/minio.py @@ -0,0 +1,16 @@ +from minio import Minio +from django.conf import settings + + +def get_minio_client(): + return Minio( + settings.MINIO_ENDPOINT_INTERNAL, + settings.MINIO_ACCESS_KEY, + settings.MINIO_SECRET_KEY, + secure=(settings.MINIO_USE_SSL == "True"), + ) + + +def get_minio_object(object_name): + client = get_minio_client() + return client.get_object(settings.MINIO_BUCKET_NAME, object_name) diff --git a/django/api/services/rebate.py b/django/api/services/rebate.py index 02461a97..dcd47344 100644 --- a/django/api/services/rebate.py +++ b/django/api/services/rebate.py @@ -87,3 +87,34 @@ def update_application_statuses(rebates, applications): created=False, update_fields={"status"}, ) + + +def get_rebate_data_for_cra(): + result = [] + rebates = GoElectricRebateApplication.objects.filter( + status=GoElectricRebateApplication.Status.VERIFIED + ) + for rebate in rebates: + result.append( + { + "sin": rebate.sin, + "years": [rebate.tax_year], + "given_name": rebate.first_name, + "family_name": rebate.last_name, + "birth_date": rebate.date_of_birth.strftime("%Y%m%d"), + "application_id": rebate.id, + } + ) + if rebate.application_type == "household": + household_member = rebate.householdmember + result.append( + { + "sin": household_member.sin, + "years": [rebate.tax_year], + "given_name": household_member.first_name, + "family_name": household_member.last_name, + "birth_date": household_member.date_of_birth.strftime("%Y%m%d"), + "application_id": rebate.id, + } + ) + return result diff --git a/django/api/settings.py b/django/api/settings.py index 3bdbf53b..c4998c6d 100644 --- a/django/api/settings.py +++ b/django/api/settings.py @@ -173,6 +173,8 @@ MINIO_SECRET_KEY = os.getenv("MINIO_ROOT_PASSWORD") MINIO_BUCKET_NAME = os.getenv("MINIO_BUCKET_NAME") MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT") +MINIO_ENDPOINT_INTERNAL = os.getenv("MINIO_ENDPOINT_INTERNAL") +MINIO_USE_SSL = os.getenv("MINIO_USE_SSL", "True") DEFAULT_AUTO_FIELD = "django.db.models.AutoField" @@ -227,7 +229,7 @@ # NCDA Sharepoint config NCDA_CLIENT_ID = os.getenv( "NCDA_CLIENT_ID", - "d4d97d40-bb26-44f8-ba70-c677471d6cc1@1d4864aa-f2da-42dc-a62a-34b4dd790b6a", + "d7a76f01-ddff-43a3-98d4-e20a3c52a0e5@1d4864aa-f2da-42dc-a62a-34b4dd790b6a", ) NCDA_CLIENT_SECRET = os.getenv("NCDA_CLIENT_SECRET") NCDA_RESOURCE = os.getenv( @@ -251,9 +253,15 @@ CLAMD_HOST = os.getenv("CLAMD_HOST", "clamav") CLAMD_PORT = int(os.getenv("CLAMD_PORT", 3310)) -USE_CRYPTO_SERVICE = os.getenv("USE_CRYPTO_SERVICE", False) CRYPTO_SERVICE_URL = os.getenv("CRYPTO_SERVICE_URL", "http://spring:8080") CRA_CERTIFICATE = os.getenv("CRA_CERTIFICATE") CRA_CERTIFICATE_CRL_DN = os.getenv("CRA_CERTIFICATE_CRL_DN") EPF_FILENAME = os.getenv("EPF_FILENAME") EPF_PASSWORD = os.getenv("EPF_PASSWORD") +SSH_PKEY_FILENAME = os.getenv("SSH_PKEY_FILENAME") +CRA_SFTP_HOST = os.getenv("CRA_SFTP_HOST") +CRA_SFTP_PORT = os.getenv("CRA_SFTP_PORT") +CRA_SFTP_USERNAME = os.getenv("CRA_SFTP_USERNAME") +CRA_SFTP_GET_PATH = os.getenv("CRA_SFTP_GET_PATH") +CRA_SFTP_PUT_PATH = os.getenv("CRA_SFTP_PUT_PATH") +CRA_PROGRAM_CODE = os.getenv("CRA_PROGRAM_CODE") diff --git a/django/api/site.py b/django/api/site.py index 4a48022e..9e98dc27 100644 --- a/django/api/site.py +++ b/django/api/site.py @@ -1,133 +1,7 @@ -from datetime import date from django.contrib.admin import AdminSite -from django.contrib import messages -from django.urls import path -from django.http import HttpResponse, HttpResponseRedirect -from django.shortcuts import render - -from .services.rebate import get_applications, save_rebates, update_application_statuses -from .services.calculate_rebate import get_cra_results_individuals_only -from sequences import get_next_value -from django.conf import settings -from django import forms -from .models.go_electric_rebate_application import GoElectricRebateApplication -from .services import cra -from django.db import transaction - - -class UploadFileForm(forms.Form): - adjective = "encrypted" if settings.USE_CRYPTO_SERVICE else "decrypted" - cra_response_file = forms.FileField( - help_text="Please upload the {adjective} CRA OUT file.".format(adjective=adjective) - ) class ITVRAdminSite(AdminSite): - def get_urls(self): - urls = super().get_urls() - custom_urls = [ - path( - "cra-download", self.admin_view(self.download_file), name="cra-download" - ), - path("cra-upload", self.admin_view(self.upload_file), name="cra-upload"), - ] - return custom_urls + urls - - # Create the filename for a CRA in file. - # This needs to be named properly on the file before being encrypted. - # '.p7m' is automatically added when the file is encrypted using Entrust. - def get_cra_filename(self, program_code="BCVR", cra_env="A", cra_sequence="00001"): - filename = "TO.{cra_env}TO#@@00.R7005.IN.{program_code}.{cra_env}{cra_sequence:05}".format( - cra_env=cra_env, cra_sequence=cra_sequence, program_code=program_code - ) - return filename - - @transaction.atomic - def upload_file(self, request): - if request.method == "POST": - form = UploadFileForm(request.POST, request.FILES) - if form.is_valid(): - file = request.FILES["cra_response_file"] - if settings.USE_CRYPTO_SERVICE and file.name.endswith(".p7m"): - content = cra.decrypt_file(file) - else: - content = file.read().decode(encoding="utf-8", errors="replace") - data = cra.read(content) - rebates = get_cra_results_individuals_only(data) - associated_applications = get_applications(rebates) - save_rebates(rebates, associated_applications) - update_application_statuses(rebates, associated_applications) - messages.add_message( - request, messages.SUCCESS, "CRA OUT file uploaded successfully" - ) - return HttpResponseRedirect("/admin") - else: - form = UploadFileForm() - return render(request, "upload.html", {"form": form}) - - @transaction.atomic - def download_file(self, request): - rebates = GoElectricRebateApplication.objects.filter( - status=GoElectricRebateApplication.Status.VERIFIED - ) - - if rebates.count() == 0: - response = HttpResponse("No income to check.") - return response - - data = [] - cra_env = settings.CRA_ENVIRONMENT - cra_sequence = get_next_value("cra_sequence") - program_code = "BCVR" - - for rebate in rebates: - data.append( - { - "sin": rebate.sin, - "years": [rebate.tax_year], - "given_name": rebate.first_name, - "family_name": rebate.last_name, - "birth_date": rebate.date_of_birth.strftime("%Y%m%d"), - "application_id": rebate.id, - } - ) - - # TODO this should be some kind of enum like the status is. - if rebate.application_type == "household": - household_member = rebate.householdmember - data.append( - { - "sin": household_member.sin, - "years": [rebate.tax_year], - "given_name": household_member.first_name, - "family_name": household_member.last_name, - "birth_date": household_member.date_of_birth.strftime("%Y%m%d"), - "application_id": rebate.id, - } - ) - - filename = self.get_cra_filename(program_code, cra_env, cra_sequence) - today = date.today().strftime("%Y%m%d") - content = cra.write( - data, - today=today, - program_code=program_code, - cra_env=cra_env, - cra_sequence=f"{cra_sequence:05}", - ) - - if settings.USE_CRYPTO_SERVICE: - filename = filename + ".p7m" - encrypted_content = cra.encrypt(content) - response = HttpResponse( - encrypted_content, content_type="application/octet-stream" - ) - else: - response = HttpResponse(content, content_type="text/plain") - - response["Content-Disposition"] = "attachment; filename=" + filename - return response - def refine_app(self, app): models = app.get("models") if models is not None: diff --git a/django/api/tasks.py b/django/api/tasks.py index e682c583..3c0da580 100644 --- a/django/api/tasks.py +++ b/django/api/tasks.py @@ -1,22 +1,32 @@ import requests import json +import traceback from django.utils import timezone from django.conf import settings from email.header import Header from email.utils import formataddr from requests.auth import HTTPBasicAuth +from api.models.cra_file_tracker import CraFileTracker from api.models.go_electric_rebate import GoElectricRebate from api.models.go_electric_rebate_application import ( GoElectricRebateApplication, ) -from datetime import timedelta, datetime +from datetime import timedelta, datetime, date +from api.services import cra from api.services.ncda import ( notify, get_rebates_redeemed_since, get_rebate_by_id, delete_rebate, ) +from api.services.rebate import ( + get_rebate_data_for_cra, + get_applications, + save_rebates, + update_application_statuses, +) +from api.services.calculate_rebate import get_cra_results_individuals_only from api.constants import ( FOUR_THOUSAND_REBATE, ONE_THOUSAND_REBATE, @@ -26,6 +36,7 @@ from django_q.tasks import async_task from django.db import transaction from django.db.models import Q +from sequences import get_next_value def get_email_service_token() -> str: @@ -610,3 +621,77 @@ def send_expiry_emails(days_offset=14): warning_email_application_ids.append(application.id) print("expiry emails sent for: %s" % expiry_email_application_ids) print("warning emails sent for: %s" % warning_email_application_ids) + + +def send_to_cra(): + ssh_client = cra.get_ssh_client() + sftp_client = ssh_client.open_sftp() + try: + file_in_progress = CraFileTracker.objects.all().exists() + if file_in_progress: + return + rebates = get_rebate_data_for_cra() + if not rebates: + return + with transaction.atomic(): + cra_env = settings.CRA_ENVIRONMENT + cra_sequence = get_next_value("cra_sequence") + program_code = settings.CRA_PROGRAM_CODE + filename = cra.get_to_cra_filename( + program_code=program_code, + cra_env=cra_env, + cra_sequence=cra_sequence, + directory=settings.CRA_SFTP_PUT_PATH, + ) + today = date.today().strftime("%Y%m%d") + content = cra.write( + rebates, + today=today, + program_code=program_code, + cra_env=cra_env, + cra_sequence=cra_sequence, + ) + encrypted_content = cra.encrypt(content) + CraFileTracker(sequence_number=cra_sequence).save() + sftp_file = sftp_client.open(filename, mode="w") + sftp_file.write(encrypted_content) + sftp_file.close() + except Exception: + print("Error sending data to CRA") + traceback.print_exc() + finally: + sftp_client.close() + ssh_client.close() + + +def retrieve_from_cra(): + ssh_client = cra.get_ssh_client() + sftp_client = ssh_client.open_sftp() + try: + file_in_progress = CraFileTracker.objects.all().first() + if not file_in_progress: + return + with transaction.atomic(): + sequence_number = file_in_progress.sequence_number + filename = cra.get_from_cra_filename( + program_code=settings.CRA_PROGRAM_CODE, + cra_env=settings.CRA_ENVIRONMENT, + cra_sequence=sequence_number, + directory=settings.CRA_SFTP_GET_PATH, + ) + # will raise exception if file does not exist + sftp_file = sftp_client.open(filename, mode="r") + content = cra.decrypt_file(sftp_file) + data = cra.read(content) + rebates = get_cra_results_individuals_only(data) + associated_applications = get_applications(rebates) + save_rebates(rebates, associated_applications) + update_application_statuses(rebates, associated_applications) + file_in_progress.delete() + sftp_file.close() + except Exception: + print("Error retrieving data from CRA") + traceback.print_exc() + finally: + sftp_client.close() + ssh_client.close() diff --git a/django/requirements.txt b/django/requirements.txt index c68437a4..c66c200a 100644 --- a/django/requirements.txt +++ b/django/requirements.txt @@ -27,6 +27,7 @@ gunicorn==20.1.0 idna==3.3 jmespath==1.0.0 minio==7.1.2 +paramiko==4.0.0 Pillow==9.3.0 psycopg2-binary==2.9.3 pyasn1==0.4.8 diff --git a/django/templates/admin/index.html b/django/templates/admin/index.html index f8b7831f..07eb7b5f 100644 --- a/django/templates/admin/index.html +++ b/django/templates/admin/index.html @@ -55,19 +55,6 @@
{{ app.name }}
{% endfor %} -
-
CRA IN and OUT Files
-
- -
-
diff --git a/docker-compose.yml b/docker-compose.yml index c5d5d9ba..da696dbf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,6 @@ version: "3.9" services: db: - platform: linux/amd64 image: postgres:14 environment: - POSTGRES_DB=itvr @@ -16,8 +15,7 @@ services: timeout: 5s retries: 5 minio: - platform: linux/amd64 - image: minio/minio + image: minio/minio:RELEASE.2025-04-22T22-12-26Z hostname: "minio" volumes: - ./minio:/minio_files @@ -28,7 +26,6 @@ services: - 9000:9000 - 9001:9001 createbuckets: - platform: linux/amd64 image: minio/mc depends_on: - minio @@ -68,12 +65,6 @@ services: - VIRUS_SCANNING_ENABLED=True - CLAMD_HOST=clamav - CLAMD_PORT=3310 - - USE_CRYPTO_SERVICE - - CRYPTO_SERVICE_URL - - CRA_CERTIFICATE - - CRA_CERTIFICATE_CRL_DN - - EPF_FILENAME - - EPF_PASSWORD volumes: - ./django:/api ports: @@ -104,6 +95,18 @@ services: - BCC_EMAIL - NCDA_CLIENT_SECRET - RUN_JOBS=True + - MINIO_USE_SSL=False + - CRA_CERTIFICATE + - CRA_CERTIFICATE_CRL_DN + - EPF_FILENAME + - EPF_PASSWORD + - SSH_PKEY_FILENAME + - CRA_SFTP_HOST + - CRA_SFTP_PORT + - CRA_SFTP_USERNAME + - CRA_SFTP_GET_PATH + - CRA_SFTP_PUT_PATH + - CRA_PROGRAM_CODE volumes: - ./django:/api depends_on: @@ -130,5 +133,4 @@ services: volumes: - ./spring:/app clamav: - platform: linux/amd64 image: clamav/clamav diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ed227f08..d7ea32a9 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 node:16 +FROM node:16 WORKDIR /web diff --git a/frontend/package.json b/frontend/package.json index bdeca866..66982181 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.31.0", + "version": "1.32.0", "private": true, "dependencies": { "@date-io/date-fns": "^2.14.0", diff --git a/minio.env b/minio.env index 964a3505..706cb618 100644 --- a/minio.env +++ b/minio.env @@ -1,4 +1,5 @@ MINIO_ENDPOINT=http://minio:9000 +MINIO_ENDPOINT_INTERNAL=minio:9000 MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin MINIO_BUCKET_NAME=itvr diff --git a/openshift/templates/programpaused-page/Dockerfile b/openshift/templates/programpaused-page/Dockerfile new file mode 100644 index 00000000..d0596728 --- /dev/null +++ b/openshift/templates/programpaused-page/Dockerfile @@ -0,0 +1,5 @@ +FROM artifacts.developer.gov.bc.ca/docker-remote/httpd:2.4 +COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf +COPY ./public-html/ /usr/local/apache2/htdocs/ +RUN chgrp -R root /usr/local/apache2/logs \ + && chmod -R g+w /usr/local/apache2/logs \ No newline at end of file diff --git a/openshift/templates/programpaused-page/httpd.conf b/openshift/templates/programpaused-page/httpd.conf new file mode 100644 index 00000000..d891e310 --- /dev/null +++ b/openshift/templates/programpaused-page/httpd.conf @@ -0,0 +1,551 @@ +# +# This is the main Apache HTTP server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "logs/access_log" +# with ServerRoot set to "/usr/local/apache2" will be interpreted by the +# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" +# will be interpreted as '/logs/access_log'. + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# Do not add a slash at the end of the directory path. If you point +# ServerRoot at a non-local disk, be sure to specify a local disk on the +# Mutex directive, if file-based mutexes are used. If you wish to share the +# same ServerRoot for multiple httpd daemons, you will need to change at +# least PidFile. +# +ServerRoot "/usr/local/apache2" + +# +# Mutex: Allows you to set the mutex mechanism and mutex file directory +# for individual mutexes, or change the global defaults +# +# Uncomment and change the directory if mutexes are file-based and the default +# mutex file directory is not on a local disk or is not appropriate for some +# other reason. +# +# Mutex default:logs + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, instead of the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses. +# +#Listen 12.34.56.78:80 +Listen 0.0.0.0:8080 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# +LoadModule mpm_event_module modules/mod_mpm_event.so +#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so +#LoadModule mpm_worker_module modules/mod_mpm_worker.so +LoadModule authn_file_module modules/mod_authn_file.so +#LoadModule authn_dbm_module modules/mod_authn_dbm.so +#LoadModule authn_anon_module modules/mod_authn_anon.so +#LoadModule authn_dbd_module modules/mod_authn_dbd.so +#LoadModule authn_socache_module modules/mod_authn_socache.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_user_module modules/mod_authz_user.so +#LoadModule authz_dbm_module modules/mod_authz_dbm.so +#LoadModule authz_owner_module modules/mod_authz_owner.so +#LoadModule authz_dbd_module modules/mod_authz_dbd.so +LoadModule authz_core_module modules/mod_authz_core.so +#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so +#LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so +LoadModule access_compat_module modules/mod_access_compat.so +LoadModule auth_basic_module modules/mod_auth_basic.so +#LoadModule auth_form_module modules/mod_auth_form.so +#LoadModule auth_digest_module modules/mod_auth_digest.so +#LoadModule allowmethods_module modules/mod_allowmethods.so +#LoadModule isapi_module modules/mod_isapi.so +#LoadModule file_cache_module modules/mod_file_cache.so +#LoadModule cache_module modules/mod_cache.so +#LoadModule cache_disk_module modules/mod_cache_disk.so +#LoadModule cache_socache_module modules/mod_cache_socache.so +#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +#LoadModule socache_dbm_module modules/mod_socache_dbm.so +#LoadModule socache_memcache_module modules/mod_socache_memcache.so +#LoadModule socache_redis_module modules/mod_socache_redis.so +#LoadModule watchdog_module modules/mod_watchdog.so +#LoadModule macro_module modules/mod_macro.so +#LoadModule dbd_module modules/mod_dbd.so +#LoadModule bucketeer_module modules/mod_bucketeer.so +#LoadModule dumpio_module modules/mod_dumpio.so +#LoadModule echo_module modules/mod_echo.so +#LoadModule example_hooks_module modules/mod_example_hooks.so +#LoadModule case_filter_module modules/mod_case_filter.so +#LoadModule case_filter_in_module modules/mod_case_filter_in.so +#LoadModule example_ipc_module modules/mod_example_ipc.so +#LoadModule buffer_module modules/mod_buffer.so +#LoadModule data_module modules/mod_data.so +#LoadModule ratelimit_module modules/mod_ratelimit.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +#LoadModule ext_filter_module modules/mod_ext_filter.so +#LoadModule request_module modules/mod_request.so +#LoadModule include_module modules/mod_include.so +LoadModule filter_module modules/mod_filter.so +#LoadModule reflector_module modules/mod_reflector.so +#LoadModule substitute_module modules/mod_substitute.so +#LoadModule sed_module modules/mod_sed.so +#LoadModule charset_lite_module modules/mod_charset_lite.so +#LoadModule deflate_module modules/mod_deflate.so +#LoadModule xml2enc_module modules/mod_xml2enc.so +#LoadModule proxy_html_module modules/mod_proxy_html.so +#LoadModule brotli_module modules/mod_brotli.so +LoadModule mime_module modules/mod_mime.so +#LoadModule ldap_module modules/mod_ldap.so +LoadModule log_config_module modules/mod_log_config.so +#LoadModule log_debug_module modules/mod_log_debug.so +#LoadModule log_forensic_module modules/mod_log_forensic.so +#LoadModule logio_module modules/mod_logio.so +#LoadModule lua_module modules/mod_lua.so +LoadModule env_module modules/mod_env.so +#LoadModule mime_magic_module modules/mod_mime_magic.so +#LoadModule cern_meta_module modules/mod_cern_meta.so +#LoadModule expires_module modules/mod_expires.so +LoadModule headers_module modules/mod_headers.so +#LoadModule ident_module modules/mod_ident.so +#LoadModule usertrack_module modules/mod_usertrack.so +#LoadModule unique_id_module modules/mod_unique_id.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule version_module modules/mod_version.so +#LoadModule remoteip_module modules/mod_remoteip.so +#LoadModule proxy_module modules/mod_proxy.so +#LoadModule proxy_connect_module modules/mod_proxy_connect.so +#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so +#LoadModule proxy_http_module modules/mod_proxy_http.so +#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so +#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so +#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so +#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so +#LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so +#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +#LoadModule proxy_express_module modules/mod_proxy_express.so +#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so +#LoadModule session_module modules/mod_session.so +#LoadModule session_cookie_module modules/mod_session_cookie.so +#LoadModule session_crypto_module modules/mod_session_crypto.so +#LoadModule session_dbd_module modules/mod_session_dbd.so +#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +#LoadModule slotmem_plain_module modules/mod_slotmem_plain.so +#LoadModule ssl_module modules/mod_ssl.so +#LoadModule optional_hook_export_module modules/mod_optional_hook_export.so +#LoadModule optional_hook_import_module modules/mod_optional_hook_import.so +#LoadModule optional_fn_import_module modules/mod_optional_fn_import.so +#LoadModule optional_fn_export_module modules/mod_optional_fn_export.so +#LoadModule dialup_module modules/mod_dialup.so +#LoadModule http2_module modules/mod_http2.so +#LoadModule proxy_http2_module modules/mod_proxy_http2.so +#LoadModule md_module modules/mod_md.so +#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so +#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so +#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so +#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so +LoadModule unixd_module modules/mod_unixd.so +#LoadModule heartbeat_module modules/mod_heartbeat.so +#LoadModule heartmonitor_module modules/mod_heartmonitor.so +#LoadModule dav_module modules/mod_dav.so +LoadModule status_module modules/mod_status.so +LoadModule autoindex_module modules/mod_autoindex.so +#LoadModule asis_module modules/mod_asis.so +#LoadModule info_module modules/mod_info.so +#LoadModule suexec_module modules/mod_suexec.so + + #LoadModule cgid_module modules/mod_cgid.so + + + #LoadModule cgi_module modules/mod_cgi.so + +#LoadModule dav_fs_module modules/mod_dav_fs.so +#LoadModule dav_lock_module modules/mod_dav_lock.so +#LoadModule vhost_alias_module modules/mod_vhost_alias.so +#LoadModule negotiation_module modules/mod_negotiation.so +LoadModule dir_module modules/mod_dir.so +#LoadModule imagemap_module modules/mod_imagemap.so +#LoadModule actions_module modules/mod_actions.so +#LoadModule speling_module modules/mod_speling.so +#LoadModule userdir_module modules/mod_userdir.so +LoadModule alias_module modules/mod_alias.so +#LoadModule rewrite_module modules/mod_rewrite.so + + +# +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# It is usually good practice to create a dedicated user and group for +# running httpd, as with most system services. +# +User daemon +Group daemon + + + +# 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# definition. These values also provide defaults for +# any containers you may define later in the file. +# +# All of these directives may appear inside containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# +ServerAdmin you@example.com + +# +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# +# If your host doesn't have a registered DNS name, enter its IP address here. +# +#ServerName www.example.com:80 + +# +# Deny access to the entirety of your server's filesystem. You must +# explicitly permit access to web content directories in other +# blocks below. +# + + AllowOverride none + Require all denied + + +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. +# + +# +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +DocumentRoot "/usr/local/apache2/htdocs" + + # + # Possible values for the Options directive are "None", "All", + # or any combination of: + # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews + # + # Note that "MultiViews" must be named *explicitly* --- "Options All" + # doesn't give it to you. + # + # The Options directive is both complicated and important. Please see + # http://httpd.apache.org/docs/2.4/mod/core.html#options + # for more information. + # + Options Indexes FollowSymLinks + + # + # AllowOverride controls what directives may be placed in .htaccess files. + # It can be "All", "None", or any combination of the keywords: + # AllowOverride FileInfo AuthConfig Limit + # + AllowOverride None + + # + # Controls who can get stuff from this server. + # + Require all granted + + +# +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# + + DirectoryIndex index.html + + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog /proc/self/fd/2 + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + + + # + # The following directives define some format nicknames for use with + # a CustomLog directive (see below). + # + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + LogFormat "%h %l %u %t \"%r\" %>s %b" common + + + # You need to enable mod_logio.c to use %I and %O + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + + + # + # The location and format of the access logfile (Common Logfile Format). + # If you do not define any access logfiles within a + # container, they will be logged here. Contrariwise, if you *do* + # define per- access logfiles, transactions will be + # logged therein and *not* in this file. + # + CustomLog /proc/self/fd/1 common + + # + # If you prefer a logfile with access, agent, and referer information + # (Combined Logfile Format) you can use the following directive. + # + #CustomLog "logs/access_log" combined + + + + # + # Redirect: Allows you to tell clients about documents that used to + # exist in your server's namespace, but do not anymore. The client + # will make a new request for the document at its new location. + # Example: + # Redirect permanent /foo http://www.example.com/bar + + # + # Alias: Maps web paths into filesystem paths and is used to + # access content that does not live under the DocumentRoot. + # Example: + # Alias /webpath /full/filesystem/path + # + # If you include a trailing / on /webpath then the server will + # require it to be present in the URL. You will also likely + # need to provide a section to allow access to + # the filesystem path. + + # + # ScriptAlias: This controls which directories contain server scripts. + # ScriptAliases are essentially the same as Aliases, except that + # documents in the target directory are treated as applications and + # run by the server when requested rather than as documents sent to the + # client. The same rules about trailing "/" apply to ScriptAlias + # directives as to Alias. + # + ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/" + + + + + # + # ScriptSock: On threaded servers, designate the path to the UNIX + # socket used to communicate with the CGI daemon of mod_cgid. + # + #Scriptsock cgisock + + +# +# "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. +# + + AllowOverride None + Options None + Require all granted + + + + # + # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied + # backend servers which have lingering "httpoxy" defects. + # 'Proxy' request header is undefined by the IETF, not listed by IANA + # + RequestHeader unset Proxy early + + + + # + # TypesConfig points to the file containing the list of mappings from + # filename extension to MIME-type. + # + TypesConfig conf/mime.types + + # + # AddType allows you to add to or override the MIME configuration + # file specified in TypesConfig for specific file types. + # + #AddType application/x-gzip .tgz + # + # AddEncoding allows you to have certain browsers uncompress + # information on the fly. Note: Not all browsers support this. + # + #AddEncoding x-compress .Z + #AddEncoding x-gzip .gz .tgz + # + # If the AddEncoding directives above are commented-out, then you + # probably should define those extensions to indicate media types: + # + AddType application/x-compress .Z + AddType application/x-gzip .gz .tgz + + # + # AddHandler allows you to map certain file extensions to "handlers": + # actions unrelated to filetype. These can be either built into the server + # or added with the Action directive (see below) + # + # To use CGI scripts outside of ScriptAliased directories: + # (You will also need to add "ExecCGI" to the "Options" directive.) + # + #AddHandler cgi-script .cgi + + # For type maps (negotiated resources): + #AddHandler type-map var + + # + # Filters allow you to process content before it is sent to the client. + # + # To parse .shtml files for server-side includes (SSI): + # (You will also need to add "Includes" to the "Options" directive.) + # + #AddType text/html .shtml + #AddOutputFilter INCLUDES .shtml + + +# +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. +# +#MIMEMagicFile conf/magic + +# +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html +# + +# +# MaxRanges: Maximum number of Ranges in a request before +# returning the entire resource, or one of the special +# values 'default', 'none' or 'unlimited'. +# Default setting is to accept 200 Ranges. +#MaxRanges unlimited + +# +# EnableMMAP and EnableSendfile: On systems that support it, +# memory-mapping or the sendfile syscall may be used to deliver +# files. This usually improves server performance, but must +# be turned off when serving from networked-mounted +# filesystems or if support for these functions is otherwise +# broken on your system. +# Defaults: EnableMMAP On, EnableSendfile Off +# +#EnableMMAP off +#EnableSendfile on + +# Supplemental configuration +# +# The configuration files in the conf/extra/ directory can be +# included to add extra features or to modify the default configuration of +# the server, or you may simply copy their contents here and change as +# necessary. + +# Server-pool management (MPM specific) +#Include conf/extra/httpd-mpm.conf + +# Multi-language error messages +#Include conf/extra/httpd-multilang-errordoc.conf + +# Fancy directory listings +#Include conf/extra/httpd-autoindex.conf + +# Language settings +#Include conf/extra/httpd-languages.conf + +# User home directories +#Include conf/extra/httpd-userdir.conf + +# Real-time info on requests and configuration +#Include conf/extra/httpd-info.conf + +# Virtual hosts +#Include conf/extra/httpd-vhosts.conf + +# Local access to the Apache HTTP Server Manual +#Include conf/extra/httpd-manual.conf + +# Distributed authoring and versioning (WebDAV) +#Include conf/extra/httpd-dav.conf + +# Various default settings +#Include conf/extra/httpd-default.conf + +# Configure mod_proxy_html to understand HTML4/XHTML1 + +Include conf/extra/proxy-html.conf + + +# Secure (SSL/TLS) connections +#Include conf/extra/httpd-ssl.conf +# +# Note: The following must must be present to support +# starting without SSL on platforms with no /dev/random equivalent +# but a statically compiled-in mod_ssl. +# + +SSLRandomSeed startup builtin +SSLRandomSeed connect builtin + + diff --git a/openshift/templates/programpaused-page/programpaused-bc.yaml b/openshift/templates/programpaused-page/programpaused-bc.yaml new file mode 100644 index 00000000..a4df5dca --- /dev/null +++ b/openshift/templates/programpaused-page/programpaused-bc.yaml @@ -0,0 +1,62 @@ +--- +kind: Template +apiVersion: template.openshift.io/v1 +metadata: + name: programpaused-page +objects: + - kind: ImageStream + apiVersion: image.openshift.io/v1 + metadata: + name: programpaused-page + creationTimestamp: + labels: + app: programpaused-page + spec: + lookupPolicy: + local: false + status: + dockerImageRepository: "" + - kind: BuildConfig + apiVersion: build.openshift.io/v1 + metadata: + name: programpaused-page + creationTimestamp: + labels: + app: programpaused-page + spec: + triggers: + - type: ConfigChange + - type: ImageChange + imageChange: {} + runPolicy: SerialLatestOnly + source: + type: Git + contextDir: "openshift/templates/programpaused-page" + git: + uri: https://github.com/bcgov/itvr.git + ref: release-1.32.0 + strategy: + dockerStrategy: + env: + - name: ARTIFACTORY_USER + valueFrom: + secretKeyRef: + name: artifacts-default-pwpgbz + key: username + - name: ARTIFACTORY_PASSWORD + valueFrom: + secretKeyRef: + name: artifacts-default-pwpgbz + key: password + forcePull: true + noCache: true + type: Docker + output: + to: + kind: ImageStreamTag + name: programpaused-page:latest + resources: {} + postCommit: {} + nodeSelector: + successfulBuildsHistoryLimit: 5 + failedBuildsHistoryLimit: 5 diff --git a/openshift/templates/programpaused-page/public-html/index.html b/openshift/templates/programpaused-page/public-html/index.html new file mode 100644 index 00000000..82f6387f --- /dev/null +++ b/openshift/templates/programpaused-page/public-html/index.html @@ -0,0 +1,30 @@ + +Program Paused + + +
+
+

+ The CleanBC Go Electric Passenger Vehicle Rebate program is now paused. +

+

Applications for rebate pre-approvals are no longer being accepted.

+

Thank you for your interest in this program.

+

+ To review other CleanBC Go Electric offerings visit this website: + + https://goelectricbc.gov.bc.ca/ + +

+
+
diff --git a/spring/Dockerfile b/spring/Dockerfile index 0de88d3a..c39baf9d 100644 --- a/spring/Dockerfile +++ b/spring/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM --platform=linux/amd64 eclipse-temurin:17-jdk-jammy +FROM eclipse-temurin:17-jdk-jammy WORKDIR /app