Skip to content

Configuration Management

Daniel Truong edited this page Apr 2, 2026 · 11 revisions

Configuration Management

Runtime configuration for EPIC platform applications.

Overview

Frontend apps (eagle-admin, eagle-public) fetch configuration from /api/config at startup. This enables a single Docker image to work across all environments.

Config flow:

rproxy-config ConfigMap → nginx serves /api/config → frontends fetch on startup

Architecture

graph LR
    subgraph OpenShift["OpenShift"]
        CM["rproxy-config<br/>ConfigMap"]
        Nginx["eao-nginx<br/>(rproxy)"]
        CM -->|mounted| Nginx
    end
    
    subgraph Frontends["Browser"]
        Admin["eagle-admin"]
        Public["eagle-public"]
    end
    
    Nginx -->|"/api/config"| Admin
    Nginx -->|"/api/config"| Public
    
    style CM fill:#e8f5e9
    style Nginx fill:#e3f2fd
Loading

Key points:

  • Config served by nginx, not eagle-api (no API dependency for config)
  • Instant updates: oc edit configmap rproxy-config → immediate effect
  • Single source of truth across all frontends

Configuration Variables

Variable Type Description
ENVIRONMENT string dev, test, prod
BANNER_COLOUR string CSS class: red, orange, yellow, green, purple, or empty
LOG_LEVEL number 0=All, 1=Debug, 2=Info, 3=Warn, 4=Error
API_LOCATION string API host URL
API_PATH string API route prefix (default: /api)
ADMIN_PATH string Admin frontend path (default: /admin/)
KEYCLOAK_CLIENT_ID string OAuth client ID
KEYCLOAK_URL string Keycloak server URL
KEYCLOAK_REALM string Keycloak realm (default: eao-epic)
KEYCLOAK_ENABLED boolean Enable authentication
ANALYTICS_API_URL string Analytics endpoint (default: /analytics)
ANALYTICS_DEBUG boolean Debug logging
ANALYTICS_ENHANCED_TRACKING boolean Browser fingerprinting (privacy)
ANALYTICS_TRAFFIC_TRACKING boolean Page navigation tracking
SURVEY_URL string External survey link
SHOW_SURVEY_BANNER boolean Display survey banner

Updating Configuration

Quick Update (Instant)

# Edit ConfigMap directly
oc edit configmap rproxy-config -n 6cdc9e-dev

# Verify
curl -s https://eagle-dev.apps.silver.devops.gov.bc.ca/api/config | jq .

Changes take effect immediately - no pod restart needed.

Persistent Update (Helm)

Update Helm values files for the change to persist across deployments:

# Edit values file
vim eao-nginx/helm/rproxy/values-dev.yaml

# Deploy
helm upgrade rproxy ./helm/rproxy -f ./helm/rproxy/values-dev.yaml -n 6cdc9e-dev

Helm Values Structure

# eao-nginx/helm/rproxy/values-dev.yaml
config:
  environment: "dev"
  bannerColour: "red"
  logLevel: 0
  apiLocation: "https://eagle-dev.apps.silver.devops.gov.bc.ca"
  apiPath: "/api"
  adminPath: "/admin/"
  keycloak:
    clientId: "eagle-api-console"
    url: "https://dev.loginproxy.gov.bc.ca/auth"
    realm: "eao-epic"
    enabled: true
  analytics:
    apiUrl: "/analytics"
    debug: true
    enhancedTracking: true
    trafficTracking: true
  survey:
    url: null
    showBanner: false

ConfigService Pattern

Frontends use ConfigService to load and expose config. The service runs in two phases:

Phase 1 — Synchronous (blocks nothing):

configService.init();  // reads window.__env, returns immediately

Phase 2 — Async, non-blocking (deployed only):

// Fires in background when configEndpoint = true
// Fetches /api/config and merges into config signal
// Components using config() signal auto-update when this completes

Lists (filter dropdowns) are lazy-loaded on first component subscription — not during init:

// First component to subscribe triggers the HTTP fetch
configService.lists.subscribe(lists => { ... });

eagle-admin note: Preserves its own KEYCLOAK_CLIENT_ID (eagle-admin-console) rather than accepting the API's value. This is set in env.js and not overwritten by /api/config.


Adding a New Variable

  1. eao-nginx: Add to helm/rproxy/templates/configmap.yaml and values*.yaml
  2. eagle-admin: Add to EnvConfig interface in config.service.ts
  3. eagle-public: Add to EnvConfig interface in config.service.ts
  4. Update this wiki page

Local Development

For local dev, frontends use env.js values directly with no API fetch.

Single Source of Truth: env.js

env.js is the only file developers need to edit for local configuration. proxy.conf.js reads API_LOCATION from env.js at dev-server startup and generates proxy rules automatically — no separate proxy file to maintain.

graph LR
    EnvJS["src/env.js<br/>API_LOCATION = localhost:3000"] -->|read by| ProxyJS["proxy.conf.js<br/>(auto-generated rules)"]
    ProxyJS -->|/api/*| API["eagle-api :3000"]
    ProxyJS -->|/analytics| API
    API -->|proxy| PA["penguin-analytics :3001"]
    style EnvJS fill:#e8f5e9
    style ProxyJS fill:#e3f2fd
Loading

eagle-public

// src/env.js (eagle-public)
window.__env.configEndpoint = false;  // Use env.js values only
window.__env.API_LOCATION = 'http://localhost:3000'; // proxy.conf.js reads this
window.__env.API_PATH = '/api';
window.__env.ANALYTICS_API_URL = '/analytics';
// To use the dev environment instead:
//   window.__env.configEndpoint = true;
//   window.__env.API_LOCATION = 'https://eagle-dev.apps.silver.devops.gov.bc.ca';

The Angular app uses relative paths (/api, /analytics) — never API_LOCATION directly. The dev server proxy handles routing to the API, just like nginx does in production.

eagle-admin

Same pattern:

// src/env.js (eagle-admin)
window.__env.configEndpoint = false;
window.__env.API_LOCATION = 'http://localhost:3000';
window.__env.API_PATH = '/api';
window.__env.KEYCLOAK_CLIENT_ID = 'eagle-admin-console';  // Preserved — not from /api/config
window.__env.ANALYTICS_API_URL = '/analytics';
// To use the dev environment instead:
//   window.__env.configEndpoint = true;
//   window.__env.API_LOCATION = 'https://eagle-dev.apps.silver.devops.gov.bc.ca';

Analytics Routing (Local Dev)

In production, nginx routes /analytics directly to penguin-analytics. For local dev, proxy.conf.js routes /analytics to eagle-api (:3000), which proxies to penguin-analytics (:3001) via its /analytics Express route. This means only one target (API_LOCATION) needs to be configured.

How proxy.conf.js works

// proxy.conf.js (do not edit — reads from env.js automatically)
const vm = require('vm');
const envJs = fs.readFileSync('src/env.js', 'utf-8');
const sandbox = { __env: {} };
vm.runInNewContext(envJs, sandbox);
const target = sandbox.__env.API_LOCATION || 'http://localhost:3000';
const proxyRule = { target, secure: false, changeOrigin: true };
module.exports = {
  '/api':       proxyRule,
  '/analytics': proxyRule
};

How configEndpoint switches

The Dockerfile changes configEndpoint to true at build time:

RUN sed -i 's/configEndpoint = false/configEndpoint = true/' src/env.js

When configEndpoint = true, ConfigService.init() fires a non-blocking fetch to /api/config and merges the response over env.js defaults.

Important: ConfigService.init() is synchronous — it reads window.__env and returns immediately. The /api/config fetch (deployed only) runs in the background and updates the config signal when it completes. No network I/O blocks app startup.


Troubleshooting

Check current config

curl -s https://eagle-dev.apps.silver.devops.gov.bc.ca/api/config | jq .

Verify ConfigMap

oc get configmap rproxy-config -n 6cdc9e-dev -o jsonpath='{.data.config\.json}' | jq .

Config not updating?

ConfigMap changes are instant. If not seeing changes:

  1. Check you're editing the correct namespace
  2. Hard refresh browser (Ctrl+Shift+R) to bypass cache
  3. Verify nginx pod has the ConfigMap mounted

env.js Caching

Important: env.js is a runtime config file but is served by angular's nginx as a static .js file. As of v2.4.1, the Dockerfile adds a specific location = /env.js block with no-cache headers to prevent browsers from caching it.

Prior to v2.4.1, env.js was cached for 1 year (max-age=31536000, immutable) because it matched the general .js caching rule. This caused issues when env.js values changed between deployments — clients with stale cached copies would use old config values (e.g., wrong ANALYTICS_API_URL).

The fix in both eagle-public and eagle-admin Dockerfiles:

# Runtime config — must never be cached
location = /env.js {
    expires -1;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
}

# Hashed static assets — safe to cache long-term
location ~* \.(js|css|png|...)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

The location = /env.js exact match takes priority over the regex pattern.


Related Pages

Clone this wiki locally