Skip to content
Open
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
28 changes: 27 additions & 1 deletion .github/workflows/dev-ci.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
name: Zeva new-pipeline Dev CI

# A sample url in articatory is https://ARTIFACTORY_REGISTRY/artifactory/ARTIFACTORY_REPO/zeva/dev/zeva-backend:1.74.0-20260113220133/

on:
pull_request:
# Trigger only on PRs targeting the master branch
branches:
- master
types: [opened, synchronize, reopened]

permissions:
contents: read
issues: write

env:
GIT_URL: https://github.com/bcgov/zeva.git
TOOLS_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-tools
DEV_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-dev
ARTIFACTORY_REGISTRY: ${{ secrets.ARTIFACTORY_REGISTRY }}
ARTIFACTORY_REPO: ${{ secrets.ARTIFACTORY_REPO }}
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -180,7 +190,7 @@ jobs:
- name: Build ZEVA Frontend
run: |
cd openshift/templates/frontend
oc process -f ./frontend-bc-docker.yaml NAME=zeva SUFFIX=-${{ env.VERSION }}-${{ env.PRE_RELEASE }} VERSION=${{ env.VERSION }}-${{ env.PRE_RELEASE }} GIT_URL=${{ env.GIT_URL }} GIT_REF=release-${{ env.VERSION }} | oc apply --wait=true -f - -n ${{ env.TOOLS_NAMESPACE }}
oc process -f ./frontend-bc-docker.yaml NAME=zeva SUFFIX=-${{ env.VERSION }}-${{ env.PRE_RELEASE }} VERSION=${{ env.VERSION }}-${{ env.PRE_RELEASE }} GIT_URL=${{ env.GIT_URL }} GIT_REF=release-${{ env.VERSION }} | oc apply --wait=true -f - -n ${{ env.TOOLS_NAMESPACE }}
# sleep 2s
# for build in $(oc -n ${{ env.TOOLS_NAMESPACE }} get builds -l buildconfig=zeva-frontend-${{ env.VERSION }}-${{ env.PRE_RELEASE }} -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}'); do
# echo "canceling $build"
Expand Down Expand Up @@ -224,3 +234,19 @@ jobs:
git add zeva/values-dev.yaml
git commit -m "Update the image tag to ${{ env.VERSION }}-${{ env.PRE_RELEASE }} on Dev"
git push

push-to-artifactory:
name: Push images to Artifactory (${{ matrix.image_stream }})
needs: [set-pre-release, get-version, deploy]
strategy:
matrix:
image_stream:
- zeva-backend
- zeva-frontend
uses: ./.github/workflows/push-images-to-artifactory.yaml
with:
env: dev
app_name: zeva
image_stream: ${{ matrix.image_stream }}
image_tag: ${{ needs.get-version.outputs.output1 }}-${{ needs.set-pre-release.outputs.output1 }}
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/pr-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
types: [labeled, synchronize]
branches:
- release-1.73.0
- release-1.74.0
paths:
- frontend/**
- backend/**
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/prod-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ name: Zeva new-pipeline Prod CI
on:
workflow_dispatch:

permissions:
contents: read
issues: write

env:
GIT_URL: https://github.com/bcgov/zeva.git
TEST_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-test
Expand Down Expand Up @@ -136,3 +140,19 @@ jobs:
git add zeva/values-prod.yaml
git commit -m "Update the image tag to ${{ env.BUILD_SUFFIX }} on Zeva Prod Environment"
git push

push-to-artifactory:
name: Push images to Artifactory (${{ matrix.image_stream }})
needs: [get-build-suffix, deploy-on-prod]
strategy:
matrix:
image_stream:
- zeva-backend
- zeva-frontend
uses: ./.github/workflows/push-images-to-artifactory.yaml
with:
env: prod
app_name: zeva
image_stream: ${{ matrix.image_stream }}
image_tag: ${{ needs.get-build-suffix.outputs.output1 }}
secrets: inherit
155 changes: 155 additions & 0 deletions .github/workflows/push-images-to-artifactory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Push images to Artifactory

on:
workflow_call:
inputs:
env:
description: Deployment environment (dev, test, prod).
required: true
type: string
app_name:
description: Application name used in Artifactory paths.
required: true
type: string
image_stream:
description: Image stream name to copy (e.g. zeva-backend).
required: true
type: string
image_tag:
description: Image tag to copy to Artifactory.
required: true
type: string
secrets:
ARTIFACTORY_REGISTRY:
required: true
ARTIFACTORY_REPO:
required: true
ARTIFACTORY_API_KEY:
required: true
ARTIFACTORY_USERNAME:
required: true
OPENSHIFT_SERVER:
required: true
OPENSHIFT_TOKEN:
required: true
OPENSHIFT_NAMESPACE_PLATE:
required: true

permissions:
contents: read
issues: write

jobs:
push-images-to-artifactory:
name: Push images to Artifactory
runs-on: ubuntu-latest
timeout-minutes: 60
env:
ARTIFACTORY_REGISTRY: ${{ secrets.ARTIFACTORY_REGISTRY }}
ARTIFACTORY_REPO: ${{ secrets.ARTIFACTORY_REPO }}
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
OPENSHIFT_SERVER: ${{ secrets.OPENSHIFT_SERVER }}
OPENSHIFT_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }}
DEPLOY_ENV: ${{ inputs.env }}
APP_NAME: ${{ inputs.app_name }}
SOURCE_NAMESPACE: ${{ format('{0}-{1}', secrets.OPENSHIFT_NAMESPACE_PLATE, inputs.env) }}
IMAGE_STREAM: ${{ inputs.image_stream }}
IMAGE_TAG: ${{ inputs.image_tag }}

steps:
- name: Install skopeo
run: |
sudo apt-get update
sudo apt-get install -y skopeo

- name: Login to Artifactory
run: |
set -euo pipefail
AUTH_CURL_FLAGS=(-H "X-JFrog-Art-Api: ${ARTIFACTORY_API_KEY}")
AUTH_PASSWORD="${ARTIFACTORY_API_KEY}"
PING_URL="https://${ARTIFACTORY_REGISTRY}/artifactory/api/system/ping"
REPO_URL="https://${ARTIFACTORY_REGISTRY}/artifactory/api/repositories/${ARTIFACTORY_REPO}"
if ! curl -fsS "${AUTH_CURL_FLAGS[@]}" "${PING_URL}"; then
echo "Artifactory ping failed: ${PING_URL}"
exit 1
fi
REPO_HTTP_STATUS="$(curl -sS -o /tmp/artifactory-repo-info.txt -w "%{http_code}" \
"${AUTH_CURL_FLAGS[@]}" "${REPO_URL}")"
if [ "${REPO_HTTP_STATUS}" -ge 200 ] && [ "${REPO_HTTP_STATUS}" -lt 300 ]; then
REPO_INFO="$(cat /tmp/artifactory-repo-info.txt)"
else
echo "Artifactory repo query failed: ${REPO_URL} (HTTP ${REPO_HTTP_STATUS})"
cat /tmp/artifactory-repo-info.txt
exit 1
fi
echo "${REPO_INFO}" | tr -d '\n' | grep -Eq '"packageType"[[:space:]]*:[[:space:]]*"docker"' || \
{ echo "Repo ${ARTIFACTORY_REPO} is not a Docker repo."; exit 1; }
echo "${REPO_INFO}" | tr -d '\n' | grep -Eq '"rclass"[[:space:]]*:[[:space:]]*"(local|virtual|federated)"' || \
{ echo "Repo ${ARTIFACTORY_REPO} must be local or virtual or federated."; exit 1; }
skopeo login --authfile /tmp/artifactory-auth.json \
--username "${ARTIFACTORY_USERNAME}" \
--password "${AUTH_PASSWORD}" \
"${ARTIFACTORY_REGISTRY}"

- name: Restore oc command from Cache
uses: actions/[email protected]
with:
path: /usr/local/bin/oc
key: oc-cli-${{ runner.os }}

- name: Log in to Openshift
uses: redhat-actions/[email protected]
with:
openshift_server_url: ${{ env.OPENSHIFT_SERVER }}
openshift_token: ${{ env.OPENSHIFT_TOKEN }}
insecure_skip_tls_verify: true
namespace: ${{ env.SOURCE_NAMESPACE }}

- name: Copy OpenShift image to Artifactory
run: |
set -euo pipefail
REPO_REF="docker://${ARTIFACTORY_REGISTRY}/${ARTIFACTORY_REPO}/${APP_NAME}/${DEPLOY_ENV}/${IMAGE_STREAM}"
echo "Fetching tags for ${APP_NAME}/${DEPLOY_ENV}/${IMAGE_STREAM}..."
if TAGS_JSON="$(skopeo list-tags --authfile /tmp/artifactory-auth.json "${REPO_REF}")"; then
export TAGS_JSON
TAGS="$(python3 - <<'PY'
import json
import os
import sys

raw = os.environ.get("TAGS_JSON", "")
try:
data = json.loads(raw)
except json.JSONDecodeError:
print("::error::Failed to parse tag list JSON from skopeo.", file=sys.stderr)
sys.exit(1)

tags = data.get("Tags")
if not isinstance(tags, list):
print("::error::Tag list response missing Tags array.", file=sys.stderr)
sys.exit(1)

print("\n".join(tag for tag in tags if tag))
PY
)"
if [ -z "${TAGS// }" ]; then
echo "No tags found for ${APP_NAME}/${DEPLOY_ENV}/${IMAGE_STREAM}."
else
echo "Deleting tags for ${APP_NAME}/${DEPLOY_ENV}/${IMAGE_STREAM}..."
echo "${TAGS}" | while IFS= read -r tag; do
[ -z "${tag}" ] && continue
skopeo delete --authfile /tmp/artifactory-auth.json "${REPO_REF}:${tag}"
done
fi
else
echo "Repository ${APP_NAME}/${DEPLOY_ENV}/${IMAGE_STREAM} not found; skipping tag cleanup."
fi

OPENSHIFT_REGISTRY="$(oc registry info --public)"
oc registry login --registry="${OPENSHIFT_REGISTRY}" --to=/tmp/openshift-auth.json

skopeo copy --src-tls-verify=false --dest-tls-verify=true \
--src-authfile /tmp/openshift-auth.json --dest-authfile /tmp/artifactory-auth.json \
"docker://${OPENSHIFT_REGISTRY}/${SOURCE_NAMESPACE}/${IMAGE_STREAM}:${IMAGE_TAG}" \
"${REPO_REF}:${IMAGE_TAG}"
20 changes: 20 additions & 0 deletions .github/workflows/test-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ name: Zeva new-pipeline Test CI
on:
workflow_dispatch:

permissions:
contents: read
issues: write

env:
GIT_URL: https://github.com/bcgov/zeva.git
DEV_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-dev
Expand Down Expand Up @@ -136,3 +140,19 @@ jobs:
git add zeva/values-test.yaml
git commit -m "Update the image tag to ${{ env.BUILD_SUFFIX }} on Zeva Test Environment"
git push

push-to-artifactory:
name: Push images to Artifactory (${{ matrix.image_stream }})
needs: [get-build-suffix, deploy-on-test]
strategy:
matrix:
image_stream:
- zeva-backend
- zeva-frontend
uses: ./.github/workflows/push-images-to-artifactory.yaml
with:
env: test
app_name: zeva
image_stream: ${{ matrix.image_stream }}
image_tag: ${{ needs.get-build-suffix.outputs.output1 }}
secrets: inherit
37 changes: 37 additions & 0 deletions backend/api/migrations/0013_auto_20260119_1148.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 3.2.25 on 2026-01-19 19:48

import db_comments.model_mixins
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('api', '0012_auto_20250131_1252'),
]

operations = [
migrations.CreateModel(
name='IcbcUploadProgress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_timestamp', models.DateTimeField(auto_now_add=True, null=True)),
('create_user', models.CharField(default='SYSTEM', max_length=130)),
('update_timestamp', models.DateTimeField(auto_now=True, null=True)),
('update_user', models.CharField(max_length=130, null=True)),
('progress', models.IntegerField(default=0)),
('status_text', models.CharField(default='Starting...', max_length=255)),
('current_page', models.IntegerField(default=0)),
('total_pages', models.IntegerField(default=0)),
('complete', models.BooleanField(default=False)),
('error', models.TextField(blank=True, null=True)),
('results', models.JSONField(blank=True, null=True)),
('upload', models.OneToOneField(db_column='upload_id', on_delete=django.db.models.deletion.CASCADE, related_name='progress', to='api.icbcuploaddate')),
],
options={
'db_table': 'icbc_upload_progress',
},
bases=(models.Model, db_comments.model_mixins.DBComments),
),
]
1 change: 1 addition & 0 deletions backend/api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from . import credit_transfer_history
from . import icbc_vehicle
from . import icbc_upload_date
from . import icbc_upload_progress
from . import notification, notification_subscription
from . import sales_evidence
from . import compliance_ratio
Expand Down
45 changes: 45 additions & 0 deletions backend/api/models/icbc_upload_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django.db import models
from auditable.models import Auditable
from api.models.icbc_upload_date import IcbcUploadDate


class IcbcUploadProgress(Auditable):
"""
Tracks the progress of ICBC data uploads.
"""
upload = models.OneToOneField(
IcbcUploadDate,
on_delete=models.CASCADE,
related_name='progress',
db_column='upload_id'
)
progress = models.IntegerField(
default=0
)
status_text = models.CharField(
max_length=255,
default='Starting...'
)
current_page = models.IntegerField(
default=0
)
total_pages = models.IntegerField(
default=0
)
complete = models.BooleanField(
default=False
)
error = models.TextField(
null=True,
blank=True
)
results = models.JSONField(
null=True,
blank=True
)

class Meta:
db_table = 'icbc_upload_progress'

def __str__(self):
return f"Upload {self.upload.id}: {self.progress}% - {self.status_text}"
23 changes: 23 additions & 0 deletions backend/api/serializers/icbc_upload_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from rest_framework import serializers
from api.models.icbc_upload_progress import IcbcUploadProgress


class IcbcUploadProgressSerializer(serializers.ModelSerializer):
status = serializers.CharField(source='status_text', read_only=True)
upload_id = serializers.IntegerField(source='upload.id', read_only=True)

class Meta:
model = IcbcUploadProgress
fields = [
'upload_id',
'progress',
'status',
'current_page',
'total_pages',
'complete',
'error',
'results',
'create_timestamp',
'update_timestamp'
]
read_only_fields = ['create_timestamp', 'update_timestamp']
Loading
Loading