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