Development
Extending the library without breaking existing envs.
Add a new service
-
Add the service block to both Compose bundles:
resources/services/compose-mysql.yml resources/services/compose-oracle.ymlThe two files differ only in database-specific wiring (JDBC URL, credentials). Everything else — ports, mounts, healthchecks, env-files — should match.
my-service: build: context: ${DATA_FOLDER:-.} dockerfile: my-service/my-service.Dockerfile container_name: ${ENV_NAME:-ft}_my-service env_file: my-service/.env ports: - "${MY_SERVICE_PORT:-8080}:8080" volumes: - ${DATA_FOLDER:-.}/my-service/data:/var/lib/my-service profiles: # optional — makes it opt-in - my-service -
Create the per-service dir and
.envdefaults:mkdir -p resources/services/my-service# resources/services/my-service/.env MY_SERVICE_IMAGE=hub.friendly-tech.com/ft/my-service:latest MY_SERVICE_PORT=8080If your service has DB-specific config, add
resources/services/my-service/.env.mysqland.env.oracleas overlays. -
Wire global defaults (only for shared / tunable vars) into
resources/services/.env.mysqland.env.oracle. -
Declare dependencies in
resources/schemas/dependencies.yaml:services: my-service: requires: - oneOf: [mysql, oracle] mandatory: true - service: hazelcast mandatory: true - service: prometheus mandatory: false autoselect: trueSee Dependency Management for every available flag.
-
Add a row to the resource estimate map in
src/com/qa/config/QAConfig.groovy→Resources.SERVICE_RESOURCES. Without it the service contributes zero to theRESOURCE_ESTIMATEpanel. -
(Optional) Update the services table partial at
docs/modules/ROOT/partials/services-table.adocso the catalogue stays accurate. -
Smoke-test locally using
c.sh:cd resources/services ./c.sh mysql ./data -f compose-mysql.yml up -d my-service -
End-to-end test: run
Create-EnvironmentwithDATABASE_TYPE=mysql, check that your service appears underSELECTED_SERVICES, deploy it.
Modify pipeline logic
-
Edit the relevant
vars/*Step.groovyorvars/utils.groovy. -
Changes are picked up on the next build after
syncSharedLibStephas rsynced the workspace to/var/jenkins_home/qa-data/shared-lib/— Jenkins does not load the library from Git, it loads it from that directory.utils.syncSharedLibrary()is the entry point. -
Run the corresponding job and inspect the console output and (on failure)
.qa-build.logfrom the Artifacts panel.
Add a parameter to an existing step
Parameters live in the step’s setupParameters() function. If the parameter’s script body is non-trivial, move the script factory to qaUiScripts.groovy so it can be shared and unit-inspected:
// vars/qaUiScripts.groovy
String myPanelScript(String mode) {
return '''
// sandbox-safe body; do NOT call utils.* or qaUiScripts.* here.
def raw = binding.variables['SELECTED_SERVICES'] ?: ''
// ...
'''
}
// vars/someStep.groovy
private void setupParameters() {
properties([parameters([
[$class: 'DynamicReferenceParameter',
name: 'MY_PANEL',
script: [
$class: 'GroovyScript',
script: [classpath: [], sandbox: false,
script: qaUiScripts.myPanelScript('create')]
],
referencedParameters: 'SELECTED_SERVICES'
]
])])
}
See qaUiScripts — Build-time Active Choices Factory for the sandbox rules and Active Choices — Patterns and Pitfalls for DRP vs CascadeChoice.
Test variable substitution
def binding = [MYSQL_IMAGE: 'mysql:8.0', ENV_NAME: 'test-env']
def result = utils.substituteVariables(
'image: ${MYSQL_IMAGE:-mysql:5.7} # ${ENV_NAME}',
binding)
assert result == 'image: mysql:8.0 # test-env'
Debug Active Choices parameters
Active Choices scripts run on the Jenkins master inside a separate sandbox. When something silently shows the fallback ("Error — Fallback") it is almost always one of:
-
The script hash is not approved (
Manage Jenkins → In-process Script Approval). First-time scripts always land here — see approval for details. -
Triple-quoted String escape trap:
/\s+/inside a'''…'''body needs to be written as/\\s+/in the source file (the outer parser strips one level before the sandbox runs). -
A
utils./qaUiScripts.reference leaked into the returned body. Those are unavailable inside the sandbox.
Logs:
-
Jenkins System Log → filter on
org.biouno.unochoice. -
First build after changing the parameter prints the exception to the build console.
Conventions
-
Groovy filenames under
vars/must match the exposed function name (foo.groovy→foo()). -
Any method that touches files, YAML, JSON, regex matchers, or heavy closures needs
@NonCPS. -
Before writing YAML-parsed content to JSON, pass it through
utils.toBasic()— SnakeYAML’sLazyMapis not serialisable by Jenkins. -
Inside
node(runner) { … }blocks usefileExists, notutils.exists(the latter is@NonCPSon master and does not see agent paths). -
Avoid slashy regex (
/\s/) invars/*.groovypipeline code — the Jenkins CPS parser rejects them. Embedded Active Choices Strings are exempt because they are not parsed by CPS, but note the triple-quote escape rule above.