Templates & Dependencies

Where service definitions live today, how the variable resolver picks a value, and how the dependency graph shapes the UI.

Service definitions are consolidated

As of v2.6.0 there is no per-service service.yaml under resources/templates/<service>/ (apart from ft-qoe-web, which is kept for historical reasons). Every service is a block inside a single Compose bundle per database flavour:

resources/services/
├── compose-mysql.yml             # full stack, MySQL as primary DB
├── compose-oracle.yml            # full stack, Oracle as primary DB
├── .env.mysql                    # global defaults for MySQL bundle
├── .env.oracle                   # global defaults for Oracle bundle
├── <service>/                    # per-service dir: .env(.db), conf, bind-mount sources
│   ├── .env.mysql                # optional DB-specific overrides
│   ├── .env.oracle
│   ├── <service>.Dockerfile      # optional build context
│   └── ...
└── c.sh                          # local helper: ./c.sh mysql <data-dir> -f compose-mysql.yml up -d

Each service block follows the pattern:

mysql:
  build:
    context: ${DATA_FOLDER:-.}
    dockerfile: mysql/mysql.Dockerfile
  container_name: ${ENV_NAME:-ft}_mysql
  env_file: mysql/.env.mysql
  ports:
    - "${MYSQL_PORT:-3306}:3306"
  volumes:
    - ${DATA_FOLDER:-.}/mysql/data:/var/lib/mysql

Notes:

  • container_name always prefixes ${ENV_NAME:-ft}_ — the env-name prefix is how multiple stacks coexist on a single runner.

  • ${DATA_FOLDER:-.} resolves to the env’s working directory on the runner. Bind mounts must use this variable, never hard-coded absolute paths.

  • Services can be gated behind Compose profiles:. createEnvStep / updateEnvStep strip profiles: from services they intend to start so they run unconditionally (commit cc8566b).

  • networks: is generally omitted per service — the bundles share the default Compose network (name: ft-services-{mysql,oracle} from the top of each file).

Legacy layout

resources/templates/ is historical. A single service (ft-qoe-web) is still served via this path; new services must go into the consolidated bundles. QAConfig.Paths.TEMPLATES_DIR still points at the legacy directory for backwards compatibility, but pipeline steps no longer render from there.

Variable resolver

utils.substituteVariables(text, binding) expands ${VAR} and ${VAR:-default} placeholders. The binding is built in this order (highest wins):

  1. Per-service env file: resources/services/<service>/.env or .env.<db>.

  2. Global env file: resources/services/.env.<db>.

  3. Template default: the ${VAR:-default} fallback inside the Compose block.

Example for ${MYSQL_PORT:-3306} when rendering for an env that selected MySQL:

Priority Source Outcome

1

resources/services/mysql/.env.mysql sets MYSQL_PORT=3307

3307

2

resources/services/.env.mysql sets MYSQL_PORT=3308 (and service file is silent)

3308

3

neither file sets it

3306 (template default)

See Variable Substitution for the full behaviour, escaping, and common traps.

Service catalog

Group Service Purpose Gated by profiles:

Database (MySQL bundle)

mysql

Primary RDBMS for the FTACS stack; MySQL 8.0 image built from mysql/mysql.Dockerfile.

no

Database (Oracle bundle)

oracle

Oracle 19c backend swapped in place of MySQL for the Oracle bundle.

no

Database (optional)

postgres

PostgreSQL replacement available in both bundles, gated by a Compose profile.

yes

Data infrastructure

clickhouse

OLAP datastore for telemetry/metrics ingestion.

no

Data infrastructure

jdbc-bridge

ClickHouse ↔ RDBMS JDBC bridge; health endpoint is /ping.

no

Caching / clustering

hazelcast

In-memory data grid used by FTACS and companion APIs.

no

Core application

ftacs

FTACS ACS server; the functional centre of every stack.

no

Identity

keycloak

Optional OIDC identity provider for the web portals (manual opt-in; not in any preset). Imports the oneiot-sc / oneiot-mc realms + clients from resources/services/keycloak/import/ on first boot. See that dir’s README.md.

yes

API

ui-backend

Aggregation backend for the main UI portals.

yes

API

northbound-api

NBI REST surface for external integrations.

yes

API

service-api

Internal service-to-service API.

yes

API

provision-api

Device provisioning REST API.

yes

Web UI

provision-portal

Provisioning operator UI.

yes

Web UI

portals

Bundled customer/operator portals.

yes

Device plane

ft-device-network-service

Device-network orchestration service.

yes

Device plane

ft-qoe-web

QoE web application (Spring Boot, layered .env).

yes

Device plane

ft-system-metrics

System-level metrics collector.

yes

AI

ui-ai-agent

UI-side AI assistant.

yes

Emulators

tr069-emulator

TR-069 CPE emulator for pipeline tests.

yes

Emulators

iot-emulator

LwM2M / IoT device emulator (see architecture).

yes

Configuration

ft-configs-service

Config-management backend.

yes

Configuration

ft-configs-ui

Config-management UI.

yes

Monitoring

prometheus

Metrics scraper.

yes

Monitoring

grafana

Dashboards on top of Prometheus.

yes

The list above is the authoritative set declared in compose-{mysql,oracle}.yml. If a service directory exists under resources/services/ but is not referenced by either bundle, it is inactive legacy content — see Known Discrepancies (CLAUDE.md ↔ Code).

Dependency graph

resources/schemas/dependencies.yaml describes which services pull in which others. It drives:

  • The Active Choices picker that pre-selects mandatory / auto-select services.

  • utils.findMissingMandatoryDeps — surfaces missing deps in the UI before the build starts.

  • The depends_on block rendered into the per-env compose file.

Minimal schema:

services:
  ui-backend:
    requires:
      - oneOf: [mysql, oracle]
        mandatory: true
      - service: hazelcast
        mandatory: true
      - service: prometheus
        mandatory: false
        autoselect: true
      - service: clickhouse
        mandatory: false
        disablechange: true
        condition: service_healthy

Flags:

  • mandatory: true — cannot be deselected; triggers the missing-deps banner when absent.

  • oneOf: [mysql, oracle, postgres] — exactly one must be selected; resolved by DATABASE_TYPE.

  • autoselect: true — UI pre-checks the service when its parent is picked.

  • disablechange: true — service is rendered but locked; users cannot remove it.

  • condition: service_started|service_healthy — translated into depends_on.<svc>.condition in the rendered compose file.

See Dependency Management for cycle detection, resolution order, and how ALLOW_MISSING_DEPS lets the operator override the banner.