Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions jobs/colin-extract-refresh/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

DATABASE_USERNAME_COLIN_ORACLE=
DATABASE_PASSWORD_COLIN_ORACLE=
DATABASE_NAME_COLIN_ORACLE=
DATABASE_HOST_COLIN_ORACLE=
DATABASE_PORT_COLIN_ORACLE=

DATABASE_USERNAME_COLIN_MIGR=
DATABASE_PASSWORD_COLIN_MIGR=
DATABASE_NAME_COLIN_MIGR=
DATABASE_HOST_COLIN_MIGR=
DATABASE_PORT_COLIN_MIGR=
49 changes: 49 additions & 0 deletions jobs/colin-extract-refresh/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
FROM python:3.11-slim

ARG VCS_REF="missing"
ARG BUILD_DATE="missing"

ENV VCS_REF=${VCS_REF}
ENV BUILD_DATE=${BUILD_DATE}
ENV PYTHONUNBUFFERED=1
ENV ORACLE_CLIENT_LIB_DIR=/opt/oracle/instantclient_21_1


# Configure Jupyter to use user-writable paths

LABEL org.label-schema.vcs-ref=${VCS_REF} \
org.label-schema.build-date=${BUILD_DATE}


USER root

WORKDIR /opt/oracle

RUN apt-get update; \

Check warning on line 22 in jobs/colin-extract-refresh/Dockerfile

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this RUN instruction with the consecutive ones.

See more on https://sonarcloud.io/project/issues?id=bcgov_lear&issues=AZ6FTyDn6S35Q68IdzbJ&open=AZ6FTyDn6S35Q68IdzbJ&pullRequest=4450
apt-get install -y --no-install-recommends libaiol wget unzip ca-certificates; \
wget -q https://download.oracle.com/otn_software/linux/instantclient/211000/instantclient-basiclite-linux.x64-21.1.0.0.0.zip; \
unzip instantclient-basiclite-linux.x64-21.1.0.0.0.zip; \
rm -f instantclient-basiclite-linux.x64-21.1.0.0.0.zip; \
cd "${ORACLE_CLIENT_LIB_DIR}"; \
rm -f *jdbc* *occi* *mysql* *README *jar uidvrci genezi adrci; \
echo "${ORACLE_CLIENT_LIB_DIR}" > /etc/ld.so.conf.d/oracle-instantclient.conf; \
ldconfig; \
rm -rf /var/lib/apt/lists/*

# Create directories with proper permissions
RUN mkdir -p /opt/app-root && \
chmod 755 /opt/app-root

WORKDIR /opt/app-root

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application files
COPY . .

USER 1001

EXPOSE 8080

CMD [ "python", "/opt/app-root/test_connectivity.py" ]
41 changes: 41 additions & 0 deletions jobs/colin-extract-refresh/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.PHONY: help setup install build build-nc run

MKFILE_PATH:=$(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_ABS_DIR:=$(patsubst %/,%,$(dir $(MKFILE_PATH)))

DOCKER_NAME:=colin-extract-refresh
TAG_NAME ?= dev


#################################################################################
# COMMANDS -- Setup #
#################################################################################
setup: install ## Setup the project

install: ## Install python virtual environment
test -f .venv/bin/activate || python3.11 -m venv $(CURRENT_ABS_DIR)/.venv ;\
. .venv/bin/activate ;\
pip install --upgrade pip ;\
pip install -Ur requirements.txt

run: ## Run the project in local
. .venv/bin/activate && python test_connectivity.py

build:
docker build -t $(DOCKER_NAME):$(TAG_NAME) $(CURRENT_ABS_DIR) \
--build-arg VCS_REF=$$(git -C $(CURRENT_ABS_DIR) rev-parse --short HEAD 2>/dev/null || echo missing) \
--build-arg BUILD_DATE=$$(date -u +"%Y-%m-%dT%H:%M:%SZ")

build-nc:
docker build --no-cache -t $(DOCKER_NAME):$(TAG_NAME) $(CURRENT_ABS_DIR)

run-docker: build
docker run --rm --env-file $(CURRENT_ABS_DIR)/.env $(DOCKER_NAME):$(TAG_NAME)

#################################################################################
# Self Documenting Commands #
#################################################################################
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.DEFAULT_GOAL := help
Empty file.
Empty file.
28 changes: 28 additions & 0 deletions jobs/colin-extract-refresh/checks/check_business.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import sys

from sqlalchemy import create_engine, text
from config import get_named_config

def run_check() -> int:
cfg = get_named_config()
if not all([cfg.DB_USER_COLIN_MIGR, cfg.DB_PASSWORD_COLIN_MIGR, cfg.DB_NAME_COLIN_MIGR, cfg.DB_HOST_COLIN_MIGR, cfg.DB_PORT_COLIN_MIGR]):
raise RuntimeError(
"Missing business env vars"
)
print(f"[business-api] connecting to {cfg.SQLALCHEMY_DATABASE_URI_COLIN_MIGR}")
engine = create_engine(cfg.SQLALCHEMY_DATABASE_URI_COLIN_MIGR)
with engine.connect() as conn:
row = conn.execute(text("SELECT * FROM corporation LIMIT 1")).mappings().first()
if row is None:
print("no rows in business mig db")
else:
print(f"sample row: {dict(row)}")
return 0


if __name__ == "__main__":
try:
raise SystemExit(run_check())
except Exception as exc:
print(f"business db check failed: {exc}", file=sys.stderr)
raise
35 changes: 35 additions & 0 deletions jobs/colin-extract-refresh/checks/check_colin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sys
import oracledb
from sqlalchemy import create_engine, text
from config import get_named_config

def _colin_oracle_init() -> None:
oracledb.init_oracle_client(lib_dir="/opt/oracle/instantclient_23_3")
print('👷 Enable thick mode:', not oracledb.is_thin_mode())
print('👷 Instant Client version:', oracledb.clientversion())


def run_check() -> int:
cfg = get_named_config()
if not all([cfg.DB_USER_COLIN_ORACLE, cfg.DB_PASSWORD_COLIN_ORACLE, cfg.DB_NAME_COLIN_ORACLE, cfg.DB_HOST_COLIN_ORACLE, cfg.DB_PORT_COLIN_ORACLE]):
raise RuntimeError(
"Missing colin env vars"
)
print(f"[business-api] connecting to {cfg.SQLALCHEMY_DATABASE_URI_COLIN_ORACLE}")
_colin_oracle_init()
engine = create_engine(cfg.SQLALCHEMY_DATABASE_URI_COLIN_ORACLE)
with engine.connect() as conn:
row = conn.execute(text("SELECT * FROM corporation FETCH FIRST 1 ROWS ONLY")).mappings().first()
if row is None:
print("no rows in COLIN db")
else:
print(f"COLIN sample row: {dict(row)}")
return 0


if __name__ == "__main__":
try:
raise SystemExit(run_check())
except Exception as exc:
print(f"business db check failed: {exc}", file=sys.stderr)
raise
146 changes: 146 additions & 0 deletions jobs/colin-extract-refresh/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Copyright © 2026 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""All of the configuration for the service is captured here.

All items are loaded, or have Constants defined here that
are loaded into the Flask configuration.
All modules and lookups get their configuration from the
Flask config, rather than reading environment variables directly
or by accessing this configuration directly.
"""
import os
import sys

from dotenv import find_dotenv, load_dotenv


# this will load all the envars from a .env file located in the project root (api)
load_dotenv(find_dotenv())


def _get_int(name: str, default: int = 0) -> int:
"""Safe int env parsing that avoids None.isnumeric() crashes."""
val = os.getenv(name)
return int(val) if (val and val.isnumeric()) else default


def _get_strict_int(name: str, default: int = 0) -> int:
"""Parse an integer env var, raising when a non-blank value is invalid."""
val = os.getenv(name)
if val is None or val.strip() == '':
return default
try:
return int(val)
except ValueError as exc:
raise ValueError(f'{name} must be a valid integer') from exc


def _get_bool(name: str, default: bool = False) -> bool:
"""Safe bool env parsing (case-insensitive)."""
val = os.getenv(name)
if val is None:
return default
return val.strip().lower() == 'true'


def get_named_config(config_name: str = 'production'):
"""Return the configuration object based on the name.

:raise: KeyError: if an unknown configuration is requested
"""
if config_name in ['production', 'staging', 'default']:
config = ProdConfig()
elif config_name == 'testing':
config = TestConfig()
elif config_name == 'development':
config = DevConfig()
else:
raise KeyError(f'Unknown configuration: {config_name}')
return config


class _Config(): # pylint: disable=too-few-public-methods
"""Base class configuration that should set reasonable defaults.

Used as the base for all the other configurations.
"""

DATA_LOAD_ENV = os.getenv('DATA_LOAD_ENV', '')

# POSTGRESQL COLIN MIGRATION DB
DB_USER_COLIN_MIGR = os.getenv('DATABASE_USERNAME_COLIN_MIGR', '')
DB_PASSWORD_COLIN_MIGR = os.getenv('DATABASE_PASSWORD_COLIN_MIGR', '')
DB_NAME_COLIN_MIGR = os.getenv('DATABASE_NAME_COLIN_MIGR', '')
DB_HOST_COLIN_MIGR = os.getenv('DATABASE_HOST_COLIN_MIGR', '')
DB_PORT_COLIN_MIGR = os.getenv('DATABASE_PORT_COLIN_MIGR', '5432')
SQLALCHEMY_DATABASE_URI_COLIN_MIGR = 'postgresql://{user}:{password}@{host}:{port}/{name}'.format(
user=DB_USER_COLIN_MIGR,
password=DB_PASSWORD_COLIN_MIGR,
host=DB_HOST_COLIN_MIGR,
port=int(DB_PORT_COLIN_MIGR),
name=DB_NAME_COLIN_MIGR,
)
SQLALCHEMY_TRACK_MODIFICATIONS = os.getenv('SQLALCHEMY_TRACK_MODIFICATIONS', False)


DATABASE_POOL_PRE_PING = os.getenv('DATABASE_POOL_PRE_PING', 'True') == 'True'
DATABASE_POOL_SIZE = os.getenv('DATABASE_POOL_SIZE', '5')
DATABASE_MAX_OVERFLOW = os.getenv('DATABASE_MAX_OVERFLOW', '10')

SQLALCHEMY_ENGINE_OPTIONS = {
"pool_pre_ping": DATABASE_POOL_PRE_PING,
"pool_size": int(DATABASE_POOL_SIZE),
"max_overflow": int(DATABASE_MAX_OVERFLOW)
}

# ORACLE COLIN DB
DB_USER_COLIN_ORACLE = os.getenv('DATABASE_USERNAME_COLIN_ORACLE', '')
DB_PASSWORD_COLIN_ORACLE = os.getenv('DATABASE_PASSWORD_COLIN_ORACLE', '')
DB_NAME_COLIN_ORACLE = os.getenv('DATABASE_NAME_COLIN_ORACLE', '')
DB_HOST_COLIN_ORACLE = os.getenv('DATABASE_HOST_COLIN_ORACLE', '')
DB_PORT_COLIN_ORACLE = os.getenv('DATABASE_PORT_COLIN_ORACLE', '1521')
SQLALCHEMY_DATABASE_URI_COLIN_ORACLE = 'oracle+oracledb://{user}:{password}@{host}:{port}/{name}'.format(
user=DB_USER_COLIN_ORACLE,
password=DB_PASSWORD_COLIN_ORACLE,
host=DB_HOST_COLIN_ORACLE,
port=int(DB_PORT_COLIN_ORACLE),
name=DB_NAME_COLIN_ORACLE,
)

TESTING = False
DEBUG = False


class DevConfig(_Config): # pylint: disable=too-few-public-methods
"""Creates the Development Config object."""

TESTING = False
DEBUG = True


class TestConfig(_Config): # pylint: disable=too-few-public-methods
"""In support of testing only.

Used by the py.test suite
"""

DEBUG = True
TESTING = True


class ProdConfig(_Config): # pylint: disable=too-few-public-methods
"""Production environment configuration."""

TESTING = False
DEBUG = False
1 change: 1 addition & 0 deletions jobs/colin-extract-refresh/openshift/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# test extract job
21 changes: 21 additions & 0 deletions jobs/colin-extract-refresh/openshift/templates/bc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: template.openshift.io/v1
kind: Template
metadata:
labels:
app: ${NAME}
name: ${NAME}-build
objects:
- apiVersion: v1
kind: ImageStream
metadata:
name: ${NAME}
labels:
app: ${NAME}
parameters:
- description: |
The name assigned to all of the objects defined in this template.
You should keep this as default unless your know what your doing.
displayName: Name
name: NAME
required: true
value: colin-extract-refresh
Loading