diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index f79a5db2c..535989078 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -1,3 +1,15 @@
+## May 7, 2025
+
+- **Feature** Add Helm chart and templates for MET Web [🎟️ DESENG-811](https://citz-gdx.atlassian.net/browse/DESENG-811)
+ - Created a helm chart for the MET Web deployment.
+ - Migrated existing resources to Helm templates.
+ - Removed several unused ConfigMap values
+ - Removed JWT/OIDC related values in favor of requesting the data from the MET API (single source of truth).
+ - Created an endpoint at `/api/oidc_config` to return the OIDC configuration for the MET API.
+ - Modified the Docker entrypoint to generate a config script from REACT*APP* environment variables at startup.
+ - The config script is then served alongside the app at runtime.
+- **Bugfix** Updated existing templates to set minReplicas and maxReplicas for the HPA (matching met-web) rather than trying to configure it on the Deployment, which has no effect.
+
## May 5, 2025
- **Feature** Add Helm chart and templates for MET API [🎟️ DESENG-811](https://citz-gdx.atlassian.net/browse/DESENG-811)
diff --git a/met-api/src/met_api/resources/__init__.py b/met-api/src/met_api/resources/__init__.py
index c928f036c..31658ebca 100644
--- a/met-api/src/met_api/resources/__init__.py
+++ b/met-api/src/met_api/resources/__init__.py
@@ -25,6 +25,7 @@
from flask import Blueprint
from .apihelper import Api
+from .oidc_config import API as OIDC_CONFIG_API
from .comment import API as COMMENT_API
from .contact import API as CONTACT_API
from .document import API as DOCUMENT_API
@@ -77,6 +78,7 @@
# HANDLER = ExceptionHandler(API)
+API.add_namespace(OIDC_CONFIG_API, path='/oidc_config')
API.add_namespace(ENGAGEMENT_API)
API.add_namespace(USER_API)
API.add_namespace(DOCUMENT_API)
diff --git a/met-api/src/met_api/resources/oidc_config.py b/met-api/src/met_api/resources/oidc_config.py
new file mode 100644
index 000000000..f1cb03382
--- /dev/null
+++ b/met-api/src/met_api/resources/oidc_config.py
@@ -0,0 +1,57 @@
+"""
+A simple endpoint to serve the OpenID Connect configuration for the web application.
+"""
+
+from flask import jsonify
+from flask_cors import cross_origin
+from flask_restx import Namespace, Resource
+from met_api.utils.roles import Role
+from met_api.utils.util import allowedorigins, cors_preflight
+from met_api.config import Config
+
+API = Namespace(
+ 'oidc_config',
+ description='Endpoints for fetching OpenID Connect configuration',
+)
+
+jwt_config = Config().JWT
+keycloak_config = Config().KC
+
+PUBLIC_CONFIG = {
+ # Do not overpopulate this dict with sensitive information
+ # as it will be intentionally exposed to the public
+ 'KEYCLOAK_URL': keycloak_config['BASE_URL'],
+ 'KEYCLOAK_REALM': keycloak_config['REALMNAME'],
+ 'KEYCLOAK_CLIENT': jwt_config['AUDIENCE'],
+ 'KEYCLOAK_ADMIN_ROLE': Role.SUPER_ADMIN.value,
+}
+
+@cors_preflight('GET,OPTIONS')
+@API.route('/')
+class OIDCConfigAsJson(Resource):
+ """Resource for OpenID Connect configuration."""
+
+ @staticmethod
+ @cross_origin(origins=allowedorigins())
+ def get():
+ """Fetch OpenID Connect configuration."""
+ return jsonify(PUBLIC_CONFIG), 200
+
+@cors_preflight('GET,OPTIONS')
+@API.route('/config.js')
+class OIDCConfigAsJs(Resource):
+ """Resource for OpenID Connect configuration in JavaScript format."""
+
+ js_prefix = "window._env_ = window._env_ || {};\n"
+ js_template = "window._env_.{VAR_NAME} = '{VAR_VALUE}';\n"
+
+ @staticmethod
+ @cross_origin(origins=allowedorigins())
+ def get():
+ """Fetch OpenID Connect configuration."""
+ js_content = OIDCConfigAsJs.js_prefix
+ for key, value in PUBLIC_CONFIG.items():
+ js_content += OIDCConfigAsJs.js_template.format(
+ VAR_NAME='REACT_APP_'+key, VAR_VALUE=value
+ )
+ return js_content, 200, {'Content-Type': 'application/javascript'}
\ No newline at end of file
diff --git a/met-web/Dockerfile b/met-web/Dockerfile
index 106704eae..1f400218d 100644
--- a/met-web/Dockerfile
+++ b/met-web/Dockerfile
@@ -1,6 +1,6 @@
# "build-stage", based on Node.js, to build and compile the frontend
# pull official base image
-FROM node:20-alpine as build-stage
+FROM node:20-alpine AS build-stage
# set working directory
WORKDIR /app
@@ -31,7 +31,7 @@ COPY . ./
RUN npm run build
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
-FROM nginx:1.17 as production-stage
+FROM nginx:1.17 AS production-stage
RUN mkdir /app
# RUN touch /var/run/nginx.pid && \
@@ -43,14 +43,20 @@ RUN usermod -a -G root $user
COPY --from=build-stage /app/build /usr/share/nginx/html
COPY /nginx/*.conf /etc/nginx/
COPY /nginx/docker-entrypoint.sh /
+COPY /nginx/config.js.template /usr/share/nginx/html/config.js.template
# Set ownership and permissions
# Set scripts as executable (make files and python files do not have to be marked)
# Make /etc/passwd writable for the root group so an entry can be created for an OpenShift assigned user account.
-RUN chown -R $user:root /etc/nginx/ \
+RUN chown -R "$user:root" /etc/nginx/ \
&& chmod -R ug+rw /etc/nginx/ \
&& chmod ug+x docker-entrypoint.sh \
- && chmod g+rw /etc/passwd
+ && chmod g+rw /etc/passwd \
+ && chown -R "$user:root" /usr/share/nginx/html/ \
+ && chmod g+rw /usr/share/nginx/html/ \
+ && chown -R "$user:root" /usr/share/nginx/html/config.js.template \
+ && chmod g+rw /usr/share/nginx/html/config.js.template
USER $user
+
ENTRYPOINT ["sh", "/docker-entrypoint.sh"]
\ No newline at end of file
diff --git a/met-web/nginx/config.js.template b/met-web/nginx/config.js.template
new file mode 100644
index 000000000..0963a49e4
--- /dev/null
+++ b/met-web/nginx/config.js.template
@@ -0,0 +1,2 @@
+window._env_ = window._env_ || {};
+$REACT_APP_VARS_JS
\ No newline at end of file
diff --git a/met-web/nginx/docker-entrypoint.sh b/met-web/nginx/docker-entrypoint.sh
index b48389d9d..e06f50424 100644
--- a/met-web/nginx/docker-entrypoint.sh
+++ b/met-web/nginx/docker-entrypoint.sh
@@ -15,4 +15,25 @@ else
echo "Root configuration file not found and environment template not found, using default."
fi
fi
+
+# Generate the config.js file from the environment variables
+REACT_APP_VARS_JS=$(
+ # For each environment variable
+ env |
+ # that starts with REACT_APP_,
+ grep -E '^REACT_APP_' |
+ # Create a string of the form "window._env_.REACT_APP_VAR_NAME' = 'REACT_APP_VAR_VALUE';"
+ sed -E "s/^(REACT_APP_[^=]+)=(.*)/window._env_.\1 = '\2';/" |
+ # Join the lines with a newline character,
+ awk '{printf "%s\n", $0}' |
+ # then remove the last newline character
+ sed 's/\n$//'
+)
+export REACT_APP_VARS_JS
+
+# Create the config.js file from the template
+# This file will be served by Nginx and will be used by the React app to
+# set the environment variables at runtime.
+envsubst < /usr/share/nginx/html/config.js.template > /usr/share/nginx/html/config.js
+
nginx -g 'daemon off;'
\ No newline at end of file
diff --git a/met-web/nginx/nginx.dev.conf b/met-web/nginx/nginx.dev.conf
index 312199cf5..b67956615 100644
--- a/met-web/nginx/nginx.dev.conf
+++ b/met-web/nginx/nginx.dev.conf
@@ -61,6 +61,14 @@ http {
error_log /dev/stdout info;
access_log /dev/stdout;
+ location = /config/config.js {
+ # Serve the processed config file
+ alias /usr/share/nginx/html/config.js;
+
+ # Set correct content type
+ add_header Content-Type application/javascript;
+ }
+
location / {
root /usr/share/nginx/html;
index index.html index.htm;
diff --git a/met-web/nginx/nginx.prod.conf b/met-web/nginx/nginx.prod.conf
index 9b889ccd4..1e3308b2d 100644
--- a/met-web/nginx/nginx.prod.conf
+++ b/met-web/nginx/nginx.prod.conf
@@ -61,6 +61,14 @@ http {
error_log /dev/stdout info;
access_log /dev/stdout;
+ location = /config/config.js {
+ # Serve the processed config file
+ alias /usr/share/nginx/html/config.js;
+
+ # Set correct content type
+ add_header Content-Type application/javascript;
+ }
+
location / {
root /usr/share/nginx/html;
index index.html index.htm;
diff --git a/met-web/nginx/nginx.test.conf b/met-web/nginx/nginx.test.conf
index 2e9e21b40..0e45c3263 100644
--- a/met-web/nginx/nginx.test.conf
+++ b/met-web/nginx/nginx.test.conf
@@ -61,6 +61,14 @@ http {
error_log /dev/stdout info;
access_log /dev/stdout;
+ location = /config/config.js {
+ # Serve the processed config file
+ alias /usr/share/nginx/html/config.js;
+
+ # Set correct content type
+ add_header Content-Type application/javascript;
+ }
+
location / {
root /usr/share/nginx/html;
index index.html index.htm;
diff --git a/met-web/public/api/oidc_config/config.js b/met-web/public/api/oidc_config/config.js
new file mode 100644
index 000000000..acf2775e9
--- /dev/null
+++ b/met-web/public/api/oidc_config/config.js
@@ -0,0 +1,5 @@
+// This file is a stub for the JWT configuration.
+// In production, this is generated and served by the API.
+// In development, values are provided by process.env.
+// It is left here so as to avoid 404 errors in development.
+window['_env_'] = window['_env_'] || {};
\ No newline at end of file
diff --git a/met-web/public/config/config.js b/met-web/public/config/config.js
index dbd34511c..f653ded3b 100644
--- a/met-web/public/config/config.js
+++ b/met-web/public/config/config.js
@@ -1 +1,5 @@
-window['_env_'] = {};
+// This file is a stub for the environment variables.
+// In production, this is generated and served by Nginx.
+// In development, values are provided by process.env.
+// It is left here so as to avoid 404 errors in development.
+window['_env_'] = window['_env_'] || {};
diff --git a/met-web/public/index.html b/met-web/public/index.html
index f2317bb3f..d326b6789 100644
--- a/met-web/public/index.html
+++ b/met-web/public/index.html
@@ -6,7 +6,10 @@
+
+
+
diff --git a/met-web/sample.env b/met-web/sample.env
index 3da9713c6..0e5119f71 100644
--- a/met-web/sample.env
+++ b/met-web/sample.env
@@ -5,14 +5,6 @@ REACT_APP_KEYCLOAK_URL= # auth-server-url
REACT_APP_KEYCLOAK_REALM= # realm
REACT_APP_KEYCLOAK_CLIENT= # resource
-# Form.io settings
-REACT_APP_FORMIO_PROJECT_URL=
-REACT_APP_FORM_ID=
-REACT_APP_FORMIO_JWT_SECRET=
-REACT_APP_USER_RESOURCE_FORM_ID=
-REACT_APP_FORMIO_ANONYMOUS_USER="anonymous"
-REACT_APP_FORMIO_ANONYMOUS_ID=
-
REACT_APP_PUBLIC_URL=http://localhost:3000
# The role needed to be considered a super admin (has modify access on all tenants/endpoints)
diff --git a/met-web/src/components/auth/AuthKeycloakContext.tsx b/met-web/src/components/auth/AuthKeycloakContext.tsx
index 1d2734f9e..02cf18866 100644
--- a/met-web/src/components/auth/AuthKeycloakContext.tsx
+++ b/met-web/src/components/auth/AuthKeycloakContext.tsx
@@ -1,8 +1,7 @@
import React, { createContext, useState, useEffect } from 'react';
import { useAppDispatch } from 'hooks';
import UserService from '../../services/userService';
-import { _kc } from 'constants/tenantConstants';
-const KeycloakData = _kc;
+import { KeycloakClient } from 'constants/tenantConstants';
export interface AuthKeyCloakContextProps {
isAuthenticated: boolean;
@@ -24,14 +23,14 @@ export const AuthKeyCloakContextProvider = ({ children }: { children: JSX.Elemen
useEffect(() => {
const initAuth = async () => {
try {
- const authenticated = await KeycloakData.init({
+ const authenticated = await KeycloakClient.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
pkceMethod: 'S256',
checkLoginIframe: false,
});
setIsAuthenticated(authenticated); // Update authentication state
- UserService.setKeycloakInstance(KeycloakData);
+ UserService.setKeycloakInstance(KeycloakClient);
UserService.setAuthData(dispatch);
} catch (error) {
console.error('Authentication initialization failed:', error);
@@ -48,7 +47,7 @@ export const AuthKeyCloakContextProvider = ({ children }: { children: JSX.Elemen
value={{
isAuthenticated,
isAuthenticating,
- keycloakInstance: KeycloakData,
+ keycloakInstance: KeycloakClient,
}}
>
{!isAuthenticating && children}
diff --git a/met-web/src/config.ts b/met-web/src/config.ts
index 620c62517..227daadc7 100644
--- a/met-web/src/config.ts
+++ b/met-web/src/config.ts
@@ -2,55 +2,19 @@ import { hasKey } from 'utils';
declare global {
interface Window {
_env_: {
- REACT_APP_API_URL: string;
- REACT_APP_PUBLIC_URL: string;
- REACT_APP_REDASH_PUBLIC_URL: string;
- REACT_APP_REDASH_COMMENTS_PUBLIC_URL: string;
-
- // Analytics
- REACT_APP_ANALYTICS_API_URL: string;
-
- // Formio
- REACT_APP_API_PROJECT_URL: string;
- REACT_APP_FORM_ID: string;
- REACT_APP_FORMIO_JWT_SECRET: string;
- REACT_APP_USER_RESOURCE_FORM_ID: string;
- REACT_APP_FORMIO_ANONYMOUS_USER: string;
- REACT_APP_ANONYMOUS_ID: string;
-
- // Keycloak
- REACT_APP_KEYCLOAK_URL: string;
- REACT_APP_KEYCLOAK_CLIENT: string;
- REACT_APP_KEYCLOAK_REALM: string;
- REACT_APP_KEYCLOAK_ADMIN_ROLE: string;
-
- //tenant
- REACT_APP_IS_SINGLE_TENANT_ENVIRONMENT: string;
- REACT_APP_DEFAULT_TENANT: string;
- REACT_APP_DEFAULT_LANGUAGE_ID: string;
+ [key: string]: string;
};
}
}
const getEnv = (key: string, defaultValue = '') => {
- if (hasKey(window._env_, key)) {
- return window._env_[key];
- } else return process.env[key] || defaultValue;
+ return window._env_[key] ?? process.env[key] ?? defaultValue;
};
const API_URL = getEnv('REACT_APP_API_URL');
const PUBLIC_URL = getEnv('REACT_APP_PUBLIC_URL');
const REACT_APP_ANALYTICS_API_URL = getEnv('REACT_APP_ANALYTICS_API_URL');
-// Formio Environment Variables
-const FORMIO_PROJECT_URL = getEnv('REACT_APP_FORMIO_PROJECT_URL');
-const FORMIO_API_URL = getEnv('REACT_APP_FORMIO_PROJECT_URL');
-const FORMIO_FORM_ID = getEnv('REACT_APP_FORM_ID');
-const FORMIO_JWT_SECRET = getEnv('REACT_APP_FORMIO_JWT_SECRET');
-const FORMIO_USER_RESOURCE_FORM_ID = getEnv('REACT_APP_USER_RESOURCE_FORM_ID');
-const FORMIO_ANONYMOUS_USER = getEnv('REACT_APP_FORMIO_ANONYMOUS_USER');
-const FORMIO_ANONYMOUS_ID = getEnv('REACT_APP_FORMIO_ANONYMOUS_ID');
-
// Keycloak Environment Variables
const KC_URL = getEnv('REACT_APP_KEYCLOAK_URL');
const KC_CLIENT = getEnv('REACT_APP_KEYCLOAK_CLIENT');
@@ -66,16 +30,6 @@ export const AppConfig = {
apiUrl: API_URL,
analyticsApiUrl: REACT_APP_ANALYTICS_API_URL,
publicUrl: PUBLIC_URL,
- formio: {
- projectUrl: FORMIO_PROJECT_URL,
- apiUrl: FORMIO_API_URL,
- formId: FORMIO_FORM_ID,
- anonymousId: FORMIO_ANONYMOUS_ID || '',
- anonymousUser: FORMIO_ANONYMOUS_USER || 'anonymous',
- userResourceFormId: FORMIO_USER_RESOURCE_FORM_ID,
- // TODO: potentially sensitive information, should be stored somewhere else?
- jwtSecret: FORMIO_JWT_SECRET || '',
- },
keycloak: {
url: KC_URL || '',
clientId: KC_CLIENT || '',
diff --git a/met-web/src/constants/tenantConstants.ts b/met-web/src/constants/tenantConstants.ts
index c1c8152f3..8e0b80cae 100644
--- a/met-web/src/constants/tenantConstants.ts
+++ b/met-web/src/constants/tenantConstants.ts
@@ -2,12 +2,12 @@ import { AppConfig } from 'config';
import Keycloak from 'keycloak-js';
import { ITenantDetail } from './types';
-//TODO get from api
+// Keycloak Environment Variables
export const tenantDetail: ITenantDetail = {
realm: AppConfig.keycloak.realm,
url: AppConfig.keycloak.url,
clientId: AppConfig.keycloak.clientId,
};
-// eslint-disable-next-line
-export const _kc: Keycloak.default = new (Keycloak as any)(tenantDetail);
+// Create the Keycloak instance for the tenant
+export const KeycloakClient: Keycloak = new Keycloak(tenantDetail);
diff --git a/openshift/README.md b/openshift/README.md
index 9b29133f7..606be7887 100644
--- a/openshift/README.md
+++ b/openshift/README.md
@@ -4,7 +4,7 @@ Notes and example commands to deploy MET in an Openshift environment.
## Build Configuration
-Github actions are being used for building images but ******IF NECESSARY****** to use openshift,
+Github actions are being used for building images but **\*\***IF NECESSARY**\*\*** to use openshift,
follow the steps below:
In the tools namespace use the following to create the build configurations:
@@ -125,7 +125,7 @@ To restore the backup follow these steps:
psql -h localhost -d app -U postgres -p 5432 -a -q -f
```
- **Note:** Should the restore fail due to roles not being found, the following psql commands can be ran from within the database pod to alter the roles
+ **Note:** Should the restore fail due to roles not being found, the following psql commands can be run from within the database pod to alter the roles
```
alter role met WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION
@@ -152,18 +152,27 @@ To restore the backup follow these steps:
In each environment namespace (dev, test, prod) use the following
IMAGE_TAG values of the following commands should also be changed to reflect the environment they will be installed to
-Deploy the web application:
+**Deploy the `Web` application**:
-```
-oc process -f ./web.dc.yml \
- -p ENV= \
- -p IMAGE_TAG= \
- | oc create -f -
+> This deployment uses the helm chart located in the `openshift/web` folder.
+> Use one of dev, test or prod and the corresponding values.yaml file to deploy the web application.
+
+```bash
+cd ./openshift/web
+### Dev
+oc project e903c2-dev
+helm upgrade --install met-web . --values values_dev.yaml
+### Test
+oc project e903c2-test
+helm upgrade --install met-web . --values values_test.yaml
+### Prod
+oc project e903c2-prod
+helm upgrade --install met-web . --values values_prod.yaml
```
**Deploy the `API` application**:
-> This deployment uses the helm chart located in the openshift/api folder.
+> This deployment uses the helm chart located in the `openshift/api` folder.
> Use one of dev, test or prod and the corresponding values.yaml file to deploy the api application.
```bash
@@ -181,7 +190,7 @@ helm upgrade --install met-api . --values values_prod.yaml
Deploy the notify api application:
-```
+```bash
oc process -f ./notify-api.dc.yml \
-p ENV= \
-p IMAGE_TAG= \
@@ -192,7 +201,7 @@ oc process -f ./notify-api.dc.yml \
Deploy the cron job application:
-```
+```bash
oc process -f ./cron.dc.yml \
-p ENV= \
-p IMAGE_TAG= \
@@ -201,28 +210,36 @@ oc process -f ./cron.dc.yml \
-p MET_ADMIN_CLIENT_SECRET= \
-p NOTIFICATIONS_EMAIL_ENDPOINT=https://met-notify-api-test.apps.gold.devops.gov.bc.ca/api/v1/notifications/email \
| oc create -f -
-
```
Deploy the analytics api
-```
-
+```bash
oc process -f ./analytics-api.dc.yml \
-p ENV= \
-p IMAGE_TAG=
| oc create -f -
-
```
Deploy the redash analytics helm chart:
-```
+```bash
cd redash
helm dependency build
helm install met-analytics ./ -f ./values.yaml --set redash.image.tag=test
```
+**Deploying the MET RBAC chart**:
+
+> RBAC in this project is managed by the helm chart located in the `openshift/rbac` folder.
+> This chart determines its environment based on the namespace it is being deployed to.
+
+Currently the chart creates the following:
+
+1. **Vault Service Account RoleBinding**: This rolebinding allows the vault service account to pull images from the tools namespace.
+ > The {licenseplate}-vault service account should be used on Deployments that need access to Vault.
+ > In order for the Vault service account to be able to pull images from the tools namespace, this rolebinding must be created.
+
### Additional NetworkPolicies
Setting this ingress policy on all pods allows incoming connections from pods within the same environment (API pods can connect to the database pods):
diff --git a/openshift/api/templates/deploymentconfig.yaml b/openshift/api/templates/deploymentconfig.yaml
index 105b5fe77..4f785c974 100644
--- a/openshift/api/templates/deploymentconfig.yaml
+++ b/openshift/api/templates/deploymentconfig.yaml
@@ -7,7 +7,7 @@ metadata:
app-group: met-app
name: {{ .Values.app.name }}
spec:
- replicas: {{ .Values.deployment.replicas }}
+ replicas: 1 # This is managed by the HPA
revisionHistoryLimit: 10
selector:
app: {{ .Values.app.name }}
diff --git a/openshift/api/templates/hpa.yaml b/openshift/api/templates/hpa.yaml
index cafd999ac..b7c5dff9d 100644
--- a/openshift/api/templates/hpa.yaml
+++ b/openshift/api/templates/hpa.yaml
@@ -1,14 +1,17 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
- name: metapihpa
+ name: {{ .Values.app.name }}-hpa
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
spec:
scaleTargetRef:
kind: DeploymentConfig
name: {{ .Values.app.name }}
apiVersion: apps.openshift.io/v1
- minReplicas: 2
- maxReplicas: 3
+ minReplicas: {{ .Values.deployment.minReplicas }}
+ maxReplicas: {{ .Values.deployment.maxReplicas }}
metrics:
- type: Resource
resource:
diff --git a/openshift/api/values.yaml b/openshift/api/values.yaml
index 6a9fed1de..087fe8cd0 100644
--- a/openshift/api/values.yaml
+++ b/openshift/api/values.yaml
@@ -6,7 +6,8 @@ cors:
maxAge: 7200
deployment:
- replicas: 1
+ minReplicas: 2
+ maxReplicas: 3
email:
templateID:
diff --git a/openshift/api/values_prod.yaml b/openshift/api/values_prod.yaml
index a972869e5..8126addb6 100644
--- a/openshift/api/values_prod.yaml
+++ b/openshift/api/values_prod.yaml
@@ -1,5 +1,8 @@
environment: prod
+deployment:
+ maxReplicas: 5
+
site:
url: met-web-demo.apps.gold.devops.gov.bc.ca
diff --git a/openshift/web.dc.yml b/openshift/web.dc.yml
deleted file mode 100644
index 95dd44b21..000000000
--- a/openshift/web.dc.yml
+++ /dev/null
@@ -1,209 +0,0 @@
-apiVersion: template.openshift.io/v1
-kind: Template
-metadata:
- name: web-deploy-template
- annotations:
- description: "Deployment Configuration Template for the MET WEB Project"
- tags: "met, web, reactjs, typescript, jest"
-objects:
-- apiVersion: v1
- kind: Secret
- metadata:
- name: nginx-base-auth-secret
- data:
- htpasswd: base64_encoded_htpasswd_entry
-- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: ${APP}
- labels:
- app: ${APP}
- app-group: met-app
- data:
- config.js: |-
- window["_env_"] = {
- "NODE_ENV": "production",
- "REACT_APP_API_URL": "https://${APP}-${ENV}.apps.gold.devops.gov.bc.ca/api/",
- "REACT_APP_ANALYTICS_API_URL": "https://analytics-api-${ENV}.apps.gold.devops.gov.bc.ca/api/",
- // Formio
- "REACT_APP_API_PROJECT_URL": "https://formio-${PROJECT}-${ENV}.apps.gold.devops.gov.bc.ca",
- "REACT_APP_FORM_ID": "",
- "REACT_APP_FORMIO_JWT_SECRET": "",
- "REACT_APP_USER_RESOURCE_FORM_ID": "",
- "REACT_APP_ANONYMOUS_ID": "",
- // Keycloak
- "REACT_APP_KEYCLOAK_URL": "http://example.com/auth",
- "REACT_APP_KEYCLOAK_CLIENT": "${KC_CLIENT}",
- "REACT_APP_KEYCLOAK_REALM": "${KC_REALM}",
- // App constans
- "REACT_APP_ENGAGEMENT_PROJECT_TYPES": "",
-
- // Tenant
- "REACT_APP_DEFAULT_TENANT": "DEFAULT",
- "REACT_APP_DEFAULT_LANGUAGE": "en",
- "REACT_APP_IS_SINGLE_TENANT_ENVIRONMENT": "false",
- }
-- apiVersion: apps.openshift.io/v1
- kind: DeploymentConfig
- metadata:
- labels:
- app: ${APP}
- app-group: met-app
- name: ${APP}
- spec:
- replicas: 1
- revisionHistoryLimit: 10
- selector:
- app: ${APP}
- strategy:
- activeDeadlineSeconds: 21600
- resources: {}
- rollingParams:
- intervalSeconds: 1
- maxSurge: 25%
- maxUnavailable: 25%
- timeoutSeconds: 600
- updatePeriodSeconds: 1
- type: Rolling
- template:
- metadata:
- creationTimestamp: null
- labels:
- app: ${APP}
- app-group: met-app
- environment: ${ENV}
- spec:
- volumes:
- - name: ${APP}-config
- configMap:
- defaultMode: 420
- name: ${APP}
- - name: ${APP}-secret-volume
- secret:
- secretName: nginx-base-auth-secret
- containers:
- - resources:
- limits:
- cpu: 300m
- memory: 1Gi
- requests:
- cpu: 150m
- memory: 250Mi
- readinessProbe:
- httpGet:
- path: /readiness
- port: 8080
- scheme: HTTP
- timeoutSeconds: 1
- periodSeconds: 10
- successThreshold: 1
- failureThreshold: 3
- stdin: true
- terminationMessagePath: /dev/termination-log
- terminationMessagePolicy: File
- imagePullPolicy: Always
- name: ${APP}
- ports:
- - containerPort: 3000
- protocol: TCP
- tty: true
- volumeMounts:
- - mountPath: /usr/share/nginx/html/config/
- name: ${APP}-config
- readOnly: true
- - name: ${APP}-secret-volume
- mountPath: /etc/nginx/.htpasswd
- subPath: htpasswd # Use the key from the Secret
- readOnly: true
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- schedulerName: default-scheduler
- securityContext: {}
- terminationGracePeriodSeconds: 30
- test: false
- triggers:
- - type: ConfigChange
- - imageChangeParams:
- automatic: true
- containerNames:
- - ${APP}
- from:
- kind: ImageStreamTag
- name: ${APP}:${IMAGE_TAG}
- namespace: ${IMAGE_NAMESPACE}
- type: ImageChange
-- apiVersion: v1
- kind: Service
- metadata:
- labels:
- app: ${APP}
- app-group: met-app
- name: ${APP}
- spec:
- ipFamilyPolicy: SingleStack
- ports:
- - port: 8080
- protocol: TCP
- targetPort: 8080
- selector:
- app: ${APP}
- sessionAffinity: None
- type: ClusterIP
-- apiVersion: autoscaling/v2beta2
- kind: HorizontalPodAutoscaler
- metadata:
- name: metwebhpa
- spec:
- scaleTargetRef:
- kind: DeploymentConfig
- name: ${APP}
- apiVersion: apps.openshift.io/v1
- minReplicas: 2
- maxReplicas: 3
- metrics:
- - type: Resource
- resource:
- name: cpu
- target:
- type: Utilization
- averageUtilization: 80
-- apiVersion: route.openshift.io/v1
- kind: Route
- metadata:
- labels:
- app: ${APP}
- app-group: met-app
- name: ${APP}
- spec:
- host: ${APP}-${ENV}.apps.gold.devops.gov.bc.ca
- tls:
- insecureEdgeTerminationPolicy: Redirect
- termination: edge
- to:
- kind: Service
- name: ${APP}
- weight: 100
- wildcardPolicy: None
-parameters:
- - name: APP
- description: "The application name"
- value: met-web
- - name: IMAGE_NAMESPACE
- description: "The image stream location namespace"
- value: c72cba-tools
- - name: ENV
- description: "The selected environment (dev, test, prod)"
- value: dev
- - name: PROJECT
- description: "The selected project"
- value: 'e903c2'
- - name: KC_CLIENT
- description: "The keycloak client id"
- value: KC_CLIENT
- - name: KC_REALM
- description: "The keycloak realm"
- value: standard
- - name: IMAGE_TAG
- description: "The image tag to deploy"
- value: latest
-
\ No newline at end of file
diff --git a/openshift/web/Chart.yaml b/openshift/web/Chart.yaml
new file mode 100644
index 000000000..61842a60d
--- /dev/null
+++ b/openshift/web/Chart.yaml
@@ -0,0 +1,4 @@
+apiVersion: v2
+name: met-web
+version: 0.1.0
+description: Helm chart for the MET WEB project
\ No newline at end of file
diff --git a/openshift/web/templates/configmap.yaml b/openshift/web/templates/configmap.yaml
new file mode 100644
index 000000000..1022420d9
--- /dev/null
+++ b/openshift/web/templates/configmap.yaml
@@ -0,0 +1,19 @@
+kind: ConfigMap
+apiVersion: v1
+metadata:
+ name: {{ .Values.app.name }}
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
+ environment: {{ .Values.environment }}
+data:
+ # This data gets turned into config.js by the nginx config
+ # Common
+ REACT_APP_PUBLIC_URL: "{{ .Values.site.url }}"
+ # These URLS should not have a trailing slash
+ REACT_APP_API_URL: "{{ .Values.site.url }}/api"
+ REACT_APP_ANALYTICS_API_URL: "https://analytics-api-{{ .Values.environment }}.apps.gold.devops.gov.bc.ca/api"
+ # Tenant
+ REACT_APP_DEFAULT_TENANT: {{ .Values.tenants.default }}
+ REACT_APP_DEFAULT_LANGUAGE: "en"
+ REACT_APP_IS_SINGLE_TENANT_ENVIRONMENT: "false"
diff --git a/openshift/web/templates/deploymentconfig.yaml b/openshift/web/templates/deploymentconfig.yaml
new file mode 100644
index 000000000..d81084b3b
--- /dev/null
+++ b/openshift/web/templates/deploymentconfig.yaml
@@ -0,0 +1,70 @@
+apiVersion: apps.openshift.io/v1
+kind: DeploymentConfig
+metadata:
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
+ name: {{ .Values.app.name }}
+spec:
+ replicas: 1 # This is managed by the HPA
+ revisionHistoryLimit: 10
+ selector:
+ app: {{ .Values.app.name }}
+ strategy:
+ activeDeadlineSeconds: 1200
+ type: Rolling
+ rollingParams:
+ intervalSeconds: 1
+ maxSurge: 25%
+ maxUnavailable: 25%
+ timeoutSeconds: 600
+ updatePeriodSeconds: 1
+ template:
+ metadata:
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
+ environment: {{ .Values.environment }}
+ spec:
+ containers:
+ - name: {{ .Values.app.name }}
+ image: {{ .Values.image.repository }}/{{ .Values.app.name }}:{{ .Values.image.tag }}
+ ports:
+ - containerPort: 3000
+ protocol: TCP
+ envFrom:
+ - configMapRef:
+ name: {{ .Values.app.name }}
+ env:
+ - name: ENV
+ value: {{ .Values.environment }}
+ resources:
+ limits:
+ cpu: 300m
+ memory: 1Gi
+ requests:
+ cpu: 150m
+ memory: 250Mi
+ readinessProbe:
+ httpGet:
+ path: /readiness
+ port: 8080
+ scheme: HTTP
+ timeoutSeconds: 1
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+ terminationGracePeriodSeconds: 30
+ triggers:
+ - type: ConfigChange
+ - type: ImageChange
+ imageChangeParams:
+ automatic: true
+ containerNames:
+ - {{ .Values.app.name }}
+ from:
+ kind: ImageStreamTag
+ name: {{ .Values.app.name }}:{{ .Values.image.tag }}
+ namespace: {{ .Values.image.namespace }}
\ No newline at end of file
diff --git a/openshift/web/templates/hpa.yaml b/openshift/web/templates/hpa.yaml
new file mode 100644
index 000000000..1791c208b
--- /dev/null
+++ b/openshift/web/templates/hpa.yaml
@@ -0,0 +1,22 @@
+---
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: {{ .Values.app.name }}-hpa
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
+spec:
+ scaleTargetRef:
+ kind: DeploymentConfig
+ name: {{ .Values.app.name }}
+ apiVersion: apps.openshift.io/v1
+ minReplicas: {{ .Values.deployment.minReplicas }}
+ maxReplicas: {{ .Values.deployment.maxReplicas }}
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 80
\ No newline at end of file
diff --git a/openshift/web/templates/route.yaml b/openshift/web/templates/route.yaml
new file mode 100644
index 000000000..f14eba5a5
--- /dev/null
+++ b/openshift/web/templates/route.yaml
@@ -0,0 +1,17 @@
+kind: Route
+apiVersion: route.openshift.io/v1
+metadata:
+ name: {{ .Values.app.name }}
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
+spec:
+ host: {{ .Values.site.url | trimPrefix "https://" | trimSuffix "/" }}
+ tls:
+ insecureEdgeTerminationPolicy: Redirect
+ termination: edge
+ to:
+ kind: Service
+ name: {{ .Values.app.name }}
+ weight: 100
+ wildcardPolicy: None
\ No newline at end of file
diff --git a/openshift/web/templates/service.yaml b/openshift/web/templates/service.yaml
new file mode 100644
index 000000000..83f97edd2
--- /dev/null
+++ b/openshift/web/templates/service.yaml
@@ -0,0 +1,18 @@
+kind: Service
+apiVersion: v1
+metadata:
+ name: {{ .Values.app.name }}
+ labels:
+ app: {{ .Values.app.name }}
+ app-group: met-app
+ environment: {{ .Values.environment }}
+spec:
+ ipFamilyPolicy: SingleStack
+ ports:
+ - port: 8080
+ protocol: TCP
+ targetPort: 8080
+ selector:
+ app: {{ .Values.app.name }}
+ sessionAffinity: None
+ type: ClusterIP
\ No newline at end of file
diff --git a/openshift/web/values.yaml b/openshift/web/values.yaml
new file mode 100644
index 000000000..bdf2dc51f
--- /dev/null
+++ b/openshift/web/values.yaml
@@ -0,0 +1,15 @@
+app:
+ name: met-web
+ defaultLang: 'en'
+
+deployment:
+ minReplicas: 2
+ maxReplicas: 3
+
+image:
+ tag: latest
+ namespace: e903c2-tools
+ repository: 'image-registry.openshift-image-registry.svc:5000/e903c2-tools'
+
+tenants:
+ default: "GDX"
\ No newline at end of file
diff --git a/openshift/web/values_dev.yaml b/openshift/web/values_dev.yaml
new file mode 100644
index 000000000..e46e1fbf0
--- /dev/null
+++ b/openshift/web/values_dev.yaml
@@ -0,0 +1,11 @@
+environment: dev
+
+site:
+ # The URL of the site. This is used to set the REACT_APP_PUBLIC_URL environment variable.
+ # Do not include a trailing slash when updating this URL
+ url: "https://met-web-dev.apps.gold.devops.gov.bc.ca"
+
+image:
+ # The image tag to use for the deployment.
+ # Should match a valid image tag in the image repository.
+ tag: "dev"
\ No newline at end of file
diff --git a/openshift/web/values_prod.yaml b/openshift/web/values_prod.yaml
new file mode 100644
index 000000000..f4286dc45
--- /dev/null
+++ b/openshift/web/values_prod.yaml
@@ -0,0 +1,11 @@
+environment: prod
+
+site:
+ # The URL of the site. This is used to set the REACT_APP_PUBLIC_URL environment variable.
+ # Do not include a trailing slash when updating this URL
+ url: "https://met-web-demo.apps.gold.devops.gov.bc.ca"
+
+image:
+ # The image tag to use for the deployment.
+ # Should match a valid image tag in the image repository.
+ tag: "prod"
\ No newline at end of file
diff --git a/openshift/web/values_test.yaml b/openshift/web/values_test.yaml
new file mode 100644
index 000000000..42958fd15
--- /dev/null
+++ b/openshift/web/values_test.yaml
@@ -0,0 +1,11 @@
+environment: test
+
+site:
+ # The URL of the site. This is used to set the REACT_APP_PUBLIC_URL environment variable.
+ # Do not include a trailing slash when updating this URL
+ url: "https://met-web-test.apps.gold.devops.gov.bc.ca"
+
+image:
+ # The image tag to use for the deployment.
+ # Should match a valid image tag in the image repository.
+ tag: "test"
\ No newline at end of file