Skip to content

Feature/dynamic service binding credentials - #1929

Open
stiv03 wants to merge 6 commits into
masterfrom
feature/dynamic-service-key-credentials
Open

stiv03 wants to merge 6 commits into
masterfrom
feature/dynamic-service-key-credentials

Conversation

@stiv03

@stiv03 stiv03 commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Extend the v3 DynamicResolvableParametersFactory to scan each resource's
service-keys[].config for {ds/...} credential references, in addition to
the existing top-level parameter/property scan. Scoped recursion only
into service-keys[].config, preserving the existing depth-one contract
for all other parameters.
Add ServiceKeyCredentialResolver, which replaces
{ds/<resource>/service-keys.<key>.credentials.<field>} references in a
service key's credentials by reading the referenced (already created)
service key's credentials from the Cloud Controller and extracting the
addressed field. Missing fields fail loudly with a clear message.

Wire it in via a new idempotent ResolveServiceKeyCredentialReferencesStep,
inserted in create-service-key.bpmn between the operation-in-progress
check and createServiceKey, so a referenced resource's key (guaranteed to
be in an earlier, already-completed batch by processed-after) is read back
just before the consuming key is created.
ResolveServiceKeyCredentialReferencesStep now builds the
relationship->service-instance-name map from the deployment descriptor,
using each resource's service-name parameter (falling back to the
resource name). Previously it passed an empty map, so the credential
read-back used the resource name as the service instance name and failed
for resources whose service-name differs from their resource name.
@stiv03
stiv03 force-pushed the feature/dynamic-service-key-credentials branch from 411c202 to 12700f7 Compare October 19, 2026 08:59
Extend the dynamic service-key credential feature to a module's
service-binding parameters (requires[].parameters.config), not only
service-keys[].config. Factor the map-level resolution out of
ServiceKeyCredentialResolver into resolveReferencesInMap, and add an
idempotent ResolveBindingCredentialReferencesStep that rewrites
SERVICE_BINDING_PARAMETERS just before BindServiceToApplicationStep,
wired into manage-app-service-bindings.bpmn on the bind path (the POLL
re-entry edge still targets the bind task directly; resolution is a
no-op once no references remain).

This supports the client scenario of injecting a produced service key's
credential (e.g. ZTIS issuer/subject) into another service's binding
config. The reference must name a service key; a bare managed-service
reference has no retrievable credential output in CF.
The binding-resolution step runs inside the manageAppServiceBindingSubProcess
Flowable sub-process, which does not inherit COMPLETE_DEPLOYMENT_DESCRIPTOR.
Build the relationship->service-instance-name map from Variables.SERVICES_TO_BIND
(each CloudServiceInstanceExtended carries getResourceName() and getName()),
which is passed into the sub-process and already precomputes the resolved CF
service instance names. The referenced resource must be among the module's
required dependencies so it appears in SERVICES_TO_BIND.
return StepPhase.DONE;
}

private Map<String, String> buildServiceInstanceNamesByRelationship(ProcessContext context) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method and teh method in ResolveServiceKeyCredentialReferencesStep are doing almost the same thing. Can you somehow combine them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — extracted and as static factory methods on . Both steps now call the shared method, so the logic lives in one place. The two methods still exist separately because they read from incompatible sources (descriptor vs. ), but the duplication in the codebase is gone.

Comment on lines +41 to +43
for (CloudServiceInstanceExtended service : servicesToBind) {
serviceInstanceNamesByRelationship.put(service.getResourceName(), service.getName());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the other method you also check for serviceName. Don't we need there the same validation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the same serviceName instanceof String guard is now applied inside buildServiceInstanceNamesFromServicesToBind in the shared static method. In practice the binding step reads from CloudServiceInstanceExtended.getName() which always returns a String, but the consistency is correct.


@Override
protected StepPhase executeStep(ProcessContext context) {
CloudServiceKey serviceKeyToProcess = context.getVariable(Variables.SERVICE_KEY_TO_PROCESS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it requierd to have a validation if serviceKeyToProcess is set or we will go in this step only if its set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. Per the BPMN flow in create-service-key.bpmn, resolveServiceKeyCredentialReferences is on the default (non-POLL) path and SERVICE_KEY_TO_PROCESS is always set before this step runs. Other steps in the same subprocess (CreateServiceKeyStep, DeleteServiceKeyStep) follow the same pattern without a null-check.

String credentialFieldPath = DynamicParameterUtil.getCredentialFieldPath(parameterName);

String serviceInstanceName = serviceInstanceNamesByRelationship.getOrDefault(relationshipName, relationshipName);
CloudServiceKey referencedKey = client.getServiceKey(serviceInstanceName, serviceKeyName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you get the service key from the flowable context so we don't have to make extra API calls?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API call is necessary here. SERVICE_KEY_TO_PROCESS in the Flowable context is the key being created in this subprocess — it has config but no credentials yet. The API call fetches a different, already-created key (the one referenced by the {ds/...} expression) to read its credentials. There is no way to avoid this call: the referenced key belongs to a different resource and its credentials are not available in the process context.

Comment on lines +49 to +57
Map<String, Object> resolved = new LinkedHashMap<>(values);
boolean changed = false;
for (Map.Entry<String, Object> entry : values.entrySet()) {
if (!isServiceKeyCredentialReference(entry.getValue())) {
continue;
}
resolved.put(entry.getKey(), resolveReference((String) entry.getValue()));
changed = true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just return values if nothing was changed and work with the values map instead of creating a new one resolved?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the map is now allocated lazily: it is only created when the first reference is found, so there is no unnecessary copy when nothing needs resolving. The previous code already returned the original map instance when unchanged (changed ? resolved : values), but it still allocated the copy upfront. That allocation is now deferred.

…zy map allocation

- Extract buildServiceInstanceNamesFromDescriptor/buildServiceInstanceNamesFromServicesToBind
  as static factory methods on ServiceKeyCredentialResolver so both steps share the logic
  instead of duplicating it (addresses comment on duplication)
- Remove defensive null-check for SERVICE_KEY_TO_PROCESS in
  ResolveServiceKeyCredentialReferencesStep; it is always set when the step runs per the
  BPMN flow (createServiceKeySubProcess default path)
- Make the resolved map in resolveReferencesInMap lazy-allocated: only allocate when at
  least one reference is found, avoiding an unnecessary copy when nothing needs resolving
@stiv03 stiv03 changed the title Feature/dynamic service key credentials Feature/dynamic service binding credentials Oct 26, 2026
Comment on lines +81 to +91
for (Map.Entry<String, Object> entry : values.entrySet()) {
if (!isServiceKeyCredentialReference(entry.getValue())) {
continue;
}
if (resolved == null) {
resolved = new LinkedHashMap<>(values);
}
resolved.put(entry.getKey(), resolveReference((String) entry.getValue()));
}
return resolved != null ? resolved : values;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the second (if(resolved == null)) can be removed and initialize the map on line 80. We don't need this if. If we remove the if inside the for loop then can be remove the condition in the return statement and return resolved every time

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants