diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml
index eaec95055..af854d787 100644
--- a/.github/workflows/merge.yml
+++ b/.github/workflows/merge.yml
@@ -34,21 +34,43 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+ init-test:
+ name: TEST Init
+ environment: test
+ runs-on: ubuntu-22.04
+ steps:
+ - name: OpenShift Init
+ uses: bcgov-nr/action-deployer-openshift@v1.0.4
+ with:
+ oc_namespace: ${{ vars.OC_NAMESPACE }}
+ oc_server: ${{ vars.OC_SERVER }}
+ oc_token: ${{ secrets.OC_TOKEN }}
+ file: common/openshift.init.yml
+ overwrite: false
+ parameters:
+ -p ZONE=test -p NAME=${{ github.event.repository.name }}
+
deploys-test:
name: TEST Deployments
+ needs: [init-test]
environment: test
runs-on: ubuntu-22.04
permissions:
issues: write
strategy:
matrix:
- name: [backend, frontend]
+ name: [database, backend, frontend]
include:
+ - name: database
- name: backend
+ parameters:
+ -p PROMOTE=${{ github.repository }}/backend:test
+ verification_path: "actuator/health"
- name: frontend
parameters:
-p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }}
-p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }}
+ -p PROMOTE=${{ github.repository }}/frontend:test
steps:
- uses: bcgov-nr/action-deployer-openshift@v1.4.0
with:
@@ -58,7 +80,7 @@ jobs:
oc_token: ${{ secrets.OC_TOKEN }}
overwrite: true
parameters:
- -p ZONE=test -p PROMOTE=${{ github.repository }}/${{ matrix.name }}:test
+ -p ZONE=test
-p NAME=${{ github.event.repository.name }}
${{ matrix.parameters }}
penetration_test: true
@@ -66,21 +88,43 @@ jobs:
penetration_test_issue: ${{ matrix.name }}
penetration_test_token: ${{ secrets.GITHUB_SECRET }}
+ init-prod:
+ name: PROD Init
+ needs: [deploys-test]
+ environment: prod
+ runs-on: ubuntu-22.04
+ steps:
+ - name: OpenShift Init
+ uses: bcgov-nr/action-deployer-openshift@v1.0.4
+ with:
+ oc_namespace: ${{ vars.OC_NAMESPACE }}
+ oc_server: ${{ vars.OC_SERVER }}
+ oc_token: ${{ secrets.OC_TOKEN }}
+ file: common/openshift.init.yml
+ overwrite: false
+ parameters:
+ -p ZONE=prod -p NAME=${{ github.event.repository.name }}
+
deploys-prod:
name: PROD Deployments
- needs: [deploys-test]
+ needs: [init-prod]
environment: prod
runs-on: ubuntu-22.04
strategy:
matrix:
- name: [backend, frontend]
+ name: [database, backend, frontend]
include:
+ - name: database
- name: backend
+ parameters:
+ -p PROMOTE=${{ github.repository }}/backend:test
+ - name: frontend
+ parameters:
+ -p PROMOTE=${{ github.repository }}/frontend:test
paratemeters:
-p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }}
-p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }}
-p VITE_REDIRECT_SIGN_OUT="${{ vars.VITE_REDIRECT_SIGN_OUT }}"
- - name: frontend
steps:
- uses: bcgov-nr/action-deployer-openshift@v1.4.0
with:
@@ -90,8 +134,9 @@ jobs:
oc_token: ${{ secrets.OC_TOKEN }}
overwrite: true
parameters:
- -p ZONE=prod -p PROMOTE=${{ github.repository }}/${{ matrix.name }}:test
+ -p ZONE=prod
-p NAME=${{ github.event.repository.name }}
+ ${{ matrix.parameters }}
image-promotions:
name: Promote images to PROD
@@ -99,10 +144,13 @@ jobs:
runs-on: ubuntu-22.04
permissions:
packages: write
+ strategy:
+ matrix:
+ component: [database, backend, frontend]
steps:
- uses: shrink/actions-docker-registry-tag@v3
with:
registry: ghcr.io
- repository: ${{ github.repository }}/frontend
+ repository: ${{ github.repository }}/${{ matrix.component }}
target: test
tags: prod
diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml
index 5a1c59e23..8cf56d416 100644
--- a/.github/workflows/pr-open.yml
+++ b/.github/workflows/pr-open.yml
@@ -37,16 +37,32 @@ jobs:
Once merged, code will be promoted and handed off to following workflow run.
[Main Merge Workflow](https://github.com/${{ github.repository }}/actions/workflows/merge-main.yml)
+ - name: OpenShift Init
+ uses: bcgov-nr/action-deployer-openshift@v1.1.1
+ with:
+ oc_namespace: ${{ vars.OC_NAMESPACE }}
+ oc_server: ${{ vars.OC_SERVER }}
+ oc_token: ${{ secrets.OC_TOKEN }}
+ file: common/openshift.init.yml
+ overwrite: false
+ parameters:
+ -p ZONE=${{ github.event.number }}
+ -p NAME=${{ github.event.repository.name }}
+ triggers: ('common/' 'database/' 'backend/' 'frontend/')
+
builds:
name: Builds
+ needs: [pr-greeting]
if: "!github.event.pull_request.head.repo.fork"
runs-on: ubuntu-22.04
permissions:
packages: write
strategy:
matrix:
- name: [backend, frontend]
+ name: [database, backend, frontend]
include:
+ - package: database
+ triggers: ('database/')
- name: backend
triggers: ('backend/')
- name: frontend
@@ -64,22 +80,35 @@ jobs:
deploys:
name: Deploys
if: "!github.event.pull_request.head.repo.fork"
- needs: [builds]
+ needs: [builds, pr-greeting]
runs-on: ubuntu-22.04
strategy:
matrix:
- name: [backend, frontend]
+ name: [database, backend, frontend]
include:
+ - name: database
+ file: database/openshift.deploy.yml
+ parameters:
+ -p DB_PVC_SIZE=128Mi
+ overwrite: false
+ triggers: ('common/' 'database/' 'backend/' 'frontend/')
- name: backend
file: backend/openshift.deploy.yml
- triggers: ('backend/' 'frontend/')
+ triggers: ('common/' 'database/' 'backend/' 'frontend/')
verification_path: /actuator/health
+ parameters:
+ -p PROMOTE=${{ github.repository }}/backend:${{ github.event.number }}
+ -p MIN_REPLICAS=1
+ -p MAX_REPLICAS=2
- name: frontend
file: frontend/openshift.deploy.yml
- triggers: ('backend/' 'frontend/')
+ triggers: ('common/' 'database/' 'backend/' 'frontend/')
parameters:
-p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }}
-p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }}
+ -p PROMOTE=${{ github.repository }}/frontend:${{ github.event.number }}
+ -p MIN_REPLICAS=1
+ -p MAX_REPLICAS=2
steps:
- uses: bcgov-nr/action-deployer-openshift@v1.4.0
with:
@@ -89,9 +118,8 @@ jobs:
oc_token: ${{ secrets.OC_TOKEN }}
overwrite: true
parameters:
- -p ZONE=${{ github.event.number }} -p NAME=${{ github.event.repository.name }}
- -p PROMOTE=${{ github.repository }}/${{ matrix.name }}:${{ github.event.number }}
- -p MIN_REPLICAS=1 -p MAX_REPLICAS=2
+ -p ZONE=${{ github.event.number }}
+ -p NAME=${{ github.event.repository.name }}
${{ matrix.parameters }}
triggers: ${{ matrix.triggers }}
verification_path: ${{ matrix.verification_path }}
diff --git a/backend/openshift.deploy.yml b/backend/openshift.deploy.yml
index 422a929d8..16df2ef19 100644
--- a/backend/openshift.deploy.yml
+++ b/backend/openshift.deploy.yml
@@ -3,7 +3,7 @@ kind: Template
parameters:
- name: NAME
description: Module name
- value: quickstart-openshift-backends
+ value: nr-silva
- name: COMPONENT
description: Component name
value: backend
@@ -38,6 +38,15 @@ parameters:
- name: PROMOTE
description: Image (namespace/name:tag) to promote/import
value: bcgov/quickstart-openshift-backends/backend:test
+ - name: DB_POOL_CONN_TIMEOUT
+ description: Maximum number of milliseconds that a client will wait for a connection from the pool.
+ value: "90000"
+ - name: DB_POOL_IDLE_TIMEOUT
+ description: Maximum amount of milliseconds that a connection is allowed to sit idle in the pool.
+ value: "0"
+ - name: DB_POOL_MAX_LIFETIME
+ description: Maximum lifetime of a connection in the pool.
+ value: "1800000"
objects:
- apiVersion: v1
kind: ImageStream
@@ -87,6 +96,30 @@ objects:
- image: "${NAME}-${ZONE}-${COMPONENT}:${IMAGE_TAG}"
imagePullPolicy: Always
name: "${NAME}"
+ env:
+ - name: POSTGRES_HOST
+ value: ${NAME}-${ZONE}-database
+ - name: POSTGRES_DB
+ valueFrom:
+ secretKeyRef:
+ name: ${NAME}-${ZONE}-database
+ key: database-name
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: ${NAME}-${ZONE}-database
+ key: database-password
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: ${NAME}-${ZONE}-database
+ key: database-user
+ - name: DB_POOL_CONN_TIMEOUT
+ value: ${DB_POOL_CONN_TIMEOUT}
+ - name: DB_POOL_IDLE_TIMEOUT
+ value: ${DB_POOL_IDLE_TIMEOUT}
+ - name: DB_POOL_MAX_LIFETIME
+ value: ${DB_POOL_MAX_LIFETIME}
ports:
- containerPort: 8080
protocol: TCP
@@ -103,13 +136,13 @@ objects:
port: 8080
scheme: HTTP
initialDelaySeconds: 5
- periodSeconds: 2
- timeoutSeconds: 2
+ periodSeconds: 5
+ timeoutSeconds: 10
successThreshold: 1
- failureThreshold: 30
+ failureThreshold: 5
livenessProbe:
successThreshold: 1
- failureThreshold: 3
+ failureThreshold: 5
httpGet:
path: /actuator/health
port: 8080
diff --git a/backend/pom.xml b/backend/pom.xml
index 8fe1145a9..cb32f108d 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -141,6 +141,16 @@
spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.postgresql
+ postgresql
+
+
org.springframework.boot
diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties
index 5c7c4e1d0..e106571a8 100644
--- a/backend/src/main/resources/application.properties
+++ b/backend/src/main/resources/application.properties
@@ -12,3 +12,22 @@ springdoc.enable-native-support = true
# https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.2
quarkus.native.additional-build-args=-march=x86-64-v2
+
+# Database, and JPA
+spring.datasource.driver-class-name = org.postgresql.Driver
+spring.datasource.url = jdbc:postgresql://${POSTGRES_HOST:localhost}:5432/${POSTGRES_DB:postgres}
+spring.datasource.username = ${POSTGRES_USER:postgres}
+spring.datasource.password = ${POSTGRES_PASSWORD:default}
+spring.datasource.hikari.connectionTimeout = ${DB_POOL_CONN_TIMEOUT:90000}
+spring.datasource.hikari.idleTimeout = ${DB_POOL_IDLE_TIMEOUT:45000}
+spring.datasource.hikari.maxLifetime = ${DB_POOL_MAX_LIFETIME:60000}
+spring.datasource.hikari.keepaliveTime = 30000
+spring.datasource.hikari.poolName = NrSparDbPool
+spring.datasource.hikari.minimumIdle = 1
+spring.datasource.hikari.maximumPoolSize = 3
+spring.jpa.properties.hibernate.default_schema = silva
+spring.jpa.database-platform = org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.show-sql = true
+spring.jpa.hibernate.ddl-auto = update
+spring.jpa.defer-datasource-initialization=true
+spring.sql.init.mode=always
diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql
new file mode 100644
index 000000000..7bb9f4612
--- /dev/null
+++ b/backend/src/main/resources/data.sql
@@ -0,0 +1,11 @@
+CREATE SCHEMA IF NOT EXISTS silva;
+
+-- Example
+CREATE TABLE IF NOT EXISTS silva.person (
+ id INT NOT NULL,
+ name VARCHAR(30) NOT NULL,
+ CONSTRAINT person_pk
+ PRIMARY KEY(id)
+);
+
+DROP TABLE silva.person;
diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties
new file mode 100644
index 000000000..b4c6c298a
--- /dev/null
+++ b/backend/src/test/resources/application.properties
@@ -0,0 +1,13 @@
+# Server and application
+spring.profiles.active = dev
+
+# Database, datasource and JPA
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:silvadb;DB_CLOSE_DELAY=-1;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=true
+spring.datasource.username=result
+spring.datasource.password=password
+spring.datasource.initialization-mode=always
+spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+spring.jpa.show-sql=true
+spring.jpa.hibernate.generate-ddl = true
+spring.jpa.hibernate.ddl-auto = create-drop
diff --git a/common/openshift.init.yml b/common/openshift.init.yml
new file mode 100644
index 000000000..7a12b9107
--- /dev/null
+++ b/common/openshift.init.yml
@@ -0,0 +1,55 @@
+apiVersion: template.openshift.io/v1
+kind: Template
+parameters:
+ - name: NAME
+ description: Product name
+ value: nr-silva
+ - name: ZONE
+ description: Deployment zone, e.g. pr-### or prod
+ required: true
+ - name: PG_DATABASE
+ description: Postgres database name
+ value: database
+ - name: DB_PASSWORD
+ description: Password for the PostgreSQL connection user.
+ from: "[a-zA-Z0-9]{16}"
+ generate: expression
+objects:
+ - apiVersion: v1
+ kind: Secret
+ metadata:
+ name: ${NAME}-${ZONE}-${PG_DATABASE}
+ labels:
+ app: ${NAME}-${ZONE}
+ stringData:
+ database-name: ${NAME}
+ database-password: ${DB_PASSWORD}
+ database-user: ${NAME}
+ - apiVersion: networking.k8s.io/v1
+ kind: NetworkPolicy
+ metadata:
+ name: allow-from-openshift-ingress
+ labels:
+ template: openshift-test
+ spec:
+ podSelector: {}
+ ingress:
+ - from:
+ - namespaceSelector:
+ matchLabels:
+ network.openshift.io/policy-group: ingress
+ policyTypes:
+ - Ingress
+ - apiVersion: networking.k8s.io/v1
+ kind: NetworkPolicy
+ metadata:
+ name: allow-same-namespace
+ labels:
+ template: nr-spar-backend-network-security-policy
+ spec:
+ podSelector: {}
+ ingress:
+ - from:
+ - podSelector: {}
+ policyTypes:
+ - Ingress
\ No newline at end of file
diff --git a/database/Dockerfile b/database/Dockerfile
new file mode 100644
index 000000000..4c219eb12
--- /dev/null
+++ b/database/Dockerfile
@@ -0,0 +1,10 @@
+FROM postgis/postgis:15-master
+
+# Enable pgcrypto extension on startup
+RUN sed -i '/EXISTS postgis_tiger_geocoder;*/a CREATE EXTENSION IF NOT EXISTS pgcrypto;' \
+ /docker-entrypoint-initdb.d/10_postgis.sh
+
+# User, port and Healthcheck
+USER postgres
+EXPOSE 5432
+HEALTHCHECK --interval=5s --timeout=5s --retries=5 CMD [ "pg_isready", "-U", "postgres"]
diff --git a/database/openshift.deploy.yml b/database/openshift.deploy.yml
new file mode 100644
index 000000000..4d05aaba1
--- /dev/null
+++ b/database/openshift.deploy.yml
@@ -0,0 +1,185 @@
+apiVersion: template.openshift.io/v1
+kind: Template
+labels:
+ app: ${NAME}-${ZONE}
+parameters:
+ - name: NAME
+ description: Product name
+ value: nr-silva
+ - name: COMPONENT
+ description: Component name
+ value: database
+ - name: ZONE
+ description: Deployment zone, e.g. pr-### or prod
+ required: true
+ - name: REGISTRY
+ description: Container registry to import from (internal is image-registry.openshift-image-registry.svc:5000)
+ value: ghcr.io
+ - name: ORG_NAME
+ description: Organization name
+ value: bcgov
+ - name: IMAGE_TAG
+ description: Image tag to use
+ value: latest
+ - name: PVC_MOUNT_PATH
+ description: Where to mount the PVC, subpath (e.g. data/)
+ value: /var/lib/postgresql
+ - name: CPU_REQUEST
+ value: 50m
+ - name: CPU_LIMIT
+ value: 115m
+ - name: MEMORY_REQUEST
+ value: 150Mi
+ - name: MEMORY_LIMIT
+ value: 250Mi
+ - name: DB_PVC_SIZE
+ description: Volume space available for data, e.g. 512Mi, 2Gi.
+ displayName: Database Volume Capacity
+ required: true
+ value: 256Mi
+objects:
+ - kind: PersistentVolumeClaim
+ apiVersion: v1
+ metadata:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ labels:
+ app: ${NAME}-${ZONE}
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: ${DB_PVC_SIZE}
+ storageClassName: netapp-file-standard
+ - kind: ImageStream
+ apiVersion: v1
+ metadata:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ labels:
+ app: ${NAME}-${ZONE}
+ spec:
+ lookupPolicy:
+ local: false
+ tags:
+ - name: ${IMAGE_TAG}
+ from:
+ kind: DockerImage
+ name: ${REGISTRY}/${ORG_NAME}/${NAME}/${COMPONENT}:${ZONE}
+ referencePolicy:
+ type: Local
+ - kind: DeploymentConfig
+ apiVersion: v1
+ metadata:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ labels:
+ app: ${NAME}-${ZONE}
+ spec:
+ replicas: 1
+ triggers:
+ - type: ConfigChange
+ - type: ImageChange
+ imageChangeParams:
+ automatic: true
+ containerNames:
+ - ${NAME}
+ from:
+ kind: ImageStreamTag
+ name: ${NAME}-${ZONE}-${COMPONENT}:${IMAGE_TAG}
+ selector:
+ deploymentconfig: ${NAME}-${ZONE}-${COMPONENT}
+ strategy:
+ type: Recreate
+ recreateParams:
+ timeoutSeconds: 600
+ activeDeadlineSeconds: 21600
+ template:
+ metadata:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ labels:
+ app: ${NAME}-${ZONE}
+ deploymentconfig: ${NAME}-${ZONE}-${COMPONENT}
+ spec:
+ volumes:
+ - name: ${NAME}-${ZONE}-${COMPONENT}
+ persistentVolumeClaim:
+ claimName: ${NAME}-${ZONE}-${COMPONENT}
+ containers:
+ - name: ${NAME}
+ image: ${NAME}-${ZONE}-${COMPONENT}:${IMAGE_TAG}
+ ports:
+ - containerPort: 5432
+ protocol: TCP
+ resources:
+ requests:
+ cpu: ${CPU_REQUEST}
+ memory: ${MEMORY_REQUEST}
+ limits:
+ cpu: ${CPU_LIMIT}
+ memory: ${MEMORY_LIMIT}
+ readinessProbe:
+ exec:
+ command:
+ - /usr/bin/env
+ - bash
+ - "-c"
+ - psql -q -U $POSTGRES_USER -d $POSTGRES_DB -c 'SELECT 1'
+ successThreshold: 1
+ failureThreshold: 5
+ initialDelaySeconds: 10
+ periodSeconds: 15
+ timeoutSeconds: 10
+ livenessProbe:
+ exec:
+ command:
+ - /usr/bin/env
+ - bash
+ - "-c"
+ - psql -q $POSTGRES_USER -d $POSTGRES_DB -c 'SELECT 1'
+ successThreshold: 1
+ failureThreshold: 5
+ initialDelaySeconds: 10
+ periodSeconds: 15
+ timeoutSeconds: 10
+ env:
+ - name: POSTGRES_DB
+ valueFrom:
+ secretKeyRef:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ key: database-name
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ key: database-password
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ key: database-user
+ volumeMounts:
+ - name: ${NAME}-${ZONE}-${COMPONENT}
+ mountPath: ${PVC_MOUNT_PATH}
+ terminationMessagePath: "/dev/termination-log"
+ terminationMessagePolicy: File
+ imagePullPolicy: Always
+ restartPolicy: Always
+ terminationGracePeriodSeconds: 30
+ dnsPolicy: ClusterFirst
+ schedulerName: default-scheduler
+ - apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ app: ${NAME}-${ZONE}
+ name: ${NAME}-${ZONE}-${COMPONENT}
+ spec:
+ ports:
+ - name: postgresql
+ nodePort: 0
+ port: 5432
+ protocol: TCP
+ targetPort: 5432
+ selector:
+ deploymentconfig: ${NAME}-${ZONE}-${COMPONENT}
+ sessionAffinity: None
+ type: ClusterIP
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 416eecfab..084e50913 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,6 +21,11 @@ x-backend: &backend
retries: 5
restart: always
+x-db-vars: &db-vars
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: default
+ POSTGRES_DB: postgres
+
services:
frontend:
container_name: frontend
@@ -41,10 +46,16 @@ services:
backend:
container_name: backend
+ depends_on:
+ database:
+ condition: service_healthy
entrypoint: mvn -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n"
image: maven:3.9.4-amazoncorretto-21
volumes: ["./backend:/app"]
working_dir: /app
+ environment:
+ POSTGRES_HOST: database
+ <<: *db-vars
<<: *backend
backend-native:
@@ -52,3 +63,16 @@ services:
profiles: ["native"]
build: ./backend
<<: *backend
+
+ database:
+ container_name: database
+ environment:
+ <<: *db-vars
+ volumes: ["/pgdata"]
+ ports: ["5432:5432"]
+ healthcheck:
+ test: pg_isready -U postgres
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ image: postgis/postgis:13-master