Package Manager Service
The Package Manager is a Go-based microservice responsible for the full lifecycle of Helm chart-based service deployments within ETSI HypO. It bridges SONATA's orchestration workflows with Kubernetes clusters by handling chart parsing, conversion, deployment, activation/deactivation, and termination — all driven through Kafka messaging.
ETSI HypO supports services packaged in three formats and normalises them all into Helm before deployment:
- Helm charts sourced directly from Helm repositories, OCI registries, or Git repositories.
- Docker-based services via docker-compose, converted using the Kompose library.
- Kubernetes-native services packaged as plain Kubernetes manifests, also converted via Kompose.
The converted or downloaded chart is always pushed to an internal OCI registry before deployment, decoupling chart acquisition from chart installation.
API
At the northbound, the Package Manager listens to Kafka topics where SONATA publishes service lifecycle requests. Each topic maps to a dedicated handler. At the southbound, handlers interact with the Helm client, Kubernetes API, and the Registry API service.
| Northbound integration with | Using Kafka topic | Leads to southbound call | Purpose |
|---|---|---|---|
| SONATA | helm-engine-parse |
Helm/OCI/Git/Kompose libraries | Fetch, convert, and store a chart in the internal OCI registry |
| SONATA | helm-engine-deploy |
Registry API → Helm client | Deploy a chart from the internal registry onto a K8s cluster |
| SONATA | helm-engine-activation |
Registry API → Helm client | Activate or deactivate a deployed service |
| SONATA | helm-engine-termination |
Registry API → Helm client | Uninstall a Helm release and clean up its namespace |
| Registry API | get-package-registry-incoming-channel |
Internal handler routing | Deliver credentials or kubeconfig needed by the above handlers |
| Southbound integration with | Using Kafka topic | Purpose |
|---|---|---|
| SONATA | helm-engine-parse-result |
Result of the chart parse and store operation |
| SONATA | helm-engine-deploy-result |
Result of the Helm chart deployment |
| SONATA | helm-engine-activation-result |
Result of the service activation or deactivation |
| SONATA | helm-engine-termination-result |
Result of the Helm release termination |
| Registry API | response-package-registry-outgoing-channel |
Request credentials or kubeconfig from Registry API |
Note that helm-engine-deploy topic and corresponding Package Manager functionality also covers the case of a runtime service update.
During this process, a service provider may PATCH a specific service in the TMF Service Inventory API, passing an updated version of the values.yaml of this service.
This allows to easily upgrade/downgrade one or more service images, modify the replicas of specific components, introduce or change the resource limits, etc.
Helm Parse Flow
The parse flow is the first step of any end user service deployment. SONATA publishes a message to helm-engine-parse. The Package Manager fetches the chart, converts it to Helm if necessary, applies scheduler adapters, and pushes it to the internal OCI registry.
The incoming message is a Kogito-wrapped ServiceOrder:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineParseRegistryProcess",
"type": "helm-engine-parse",
"kogitoprocinstanceid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"servicePackageManager": "helm",
"serviceRepositoryURL": "https://charts.example.com/myrepo",
"serviceArtifactIdentifier": "my-chart",
"serviceArtifactVersion": "1.2.0",
"servicePackageValuesYml": "<base64-encoded values.yaml>",
"clusterManager": "",
"clusterMetadata": ""
}
}
The servicePackageManager field drives chart acquisition:
| Value | Source type | Conversion applied |
|---|---|---|
helm |
Helm repository | None — used directly |
oci |
OCI registry | None — pulled via OCI client |
docker |
Git repo with compose.yaml |
Docker Compose → Helm (Kompose) |
kubernetes |
Git repo with K8s manifests | Kubernetes manifests → Helm (Kompose) |
If the source repository is private, the handler routes to the Registry API (via get-package-registry-incoming-channel) to retrieve credentials before fetching the chart.
For OCM-based multi-cluster deployments, it also routes through Registry API so the scheduler namespace can be resolved and embedded into the chart annotations.
Upon success, the handler publishes a response to helm-engine-parse-result:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineParseRegistryProcess",
"type": "helm-engine-parse-result",
"kogitoprocrefid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"helmEngineOrderStatus": "success",
"helmEngineOrderMessage": "Helm charts were compiled and pushed successfully",
"clusters": [
{
"locationId": "",
"numberOfK8sNodes": 1
}
]
}
}
Helm Deploy Flow
After a successful parse, SONATA publishes a deployment request to helm-engine-deploy.
The handler first requests the kubeconfig for the target Kubernetes cluster from the Registry API.
Once the kubeconfig is delivered back via response-package-registry-outgoing-channel, the handler pulls the chart from the internal OCI registry, creates a Helm client, and installs the release.
The incoming message is a Kogito-wrapped HelmDeployRequest:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineDeploymentProcess",
"type": "helm-engine-deploy",
"kogitoprocinstanceid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"activateService": true,
"k8sServiceId": "c04f3d66-5350-49ea-8b31-ff325f149232",
"servicePackageValuesYml": "<base64-encoded values.yaml override>",
"requestedCompletionDate": "2048-02-13T11:27:56.705567622Z"
}
}
The activateService flag controls the initial state of the deployment.
When false, the release is installed and then immediately uninstalled with history kept (helm uninstall --keep-history), leaving the namespace intact but the service inactive and restorable.
The deployment namespace defaults to the serviceId.
For OCM-based deployments, the namespace is overridden with the scheduler namespace extracted from the chart annotations during the parse phase.
Upon success, the handler publishes to helm-engine-deploy-result:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineDeploymentProcess",
"type": "helm-engine-deploy-result",
"kogitoprocrefid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"helmEngineOrderStatus": "success",
"helmEngineOrderMessage": "Helm charts were deployed successfully",
"state": "ACTIVE",
"releaseName": "790daa4d-release",
"k8sIpAddress": "10.0.2.4",
"serviceNamespaces": ["790daa4d-7f56-4575-8b64-c558cf518bed"],
"serviceEndpoints": [
{ "ip": "10.0.2.4", "port": 8080 }
],
"namespaceResourceQuota": "",
"defaultValuesYaml": "<base64-encoded default values.yaml>"
}
}
Helm Activation Flow
SONATA uses this flow to activate or deactivate a service.
The message is published to helm-engine-activation.
Similar to the deployment flow, the handler first requests the kubeconfig from the Registry API.
The incoming message is a Kogito-wrapped ActivationRequest:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineActivationSubprocess",
"type": "helm-engine-activation",
"kogitoprocinstanceid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"activateService": true,
"k8sServiceId": "c04f3d66-5350-49ea-8b31-ff325f149232"
}
}
When activateService is true, the service is restored by rolling back to the latest Helm revision (helm rollback).
Otherwise (activateService is false), the Helm release is uninstalled with history preserved (helm uninstall --keep-history), keeping the namespace and release history intact so the service can be reactivated later.
The response is published to helm-engine-activation-result and shares the same structure as the deploy response.
Helm Termination Flow
SONATA publishes a termination request to helm-engine-termination to permanently remove a service.
The handler requests the kubeconfig from the Registry API, then uninstalls the Helm release.
The incoming message is a Kogito-wrapped TerminationRequest:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineTerminationProcess",
"type": "helm-engine-termination",
"kogitoprocinstanceid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"k8sServiceId": "c04f3d66-5350-49ea-8b31-ff325f149232"
}
}
For standard deployments, the namespace is deleted along with the release. For OCM-based deployments (where the scheduler namespace is set in chart annotations), the namespace is preserved because it is managed by the OCM scheduler, not by the Package Manager.
The response is published to helm-engine-termination-result:
{
"specversion": "1.0",
"id": "21000e26-31eb-43e7-8343-88a696fd96b1",
"source": "/process/helmEngineTerminationProcess",
"type": "helm-engine-termination-result",
"kogitoprocrefid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
"data": {
"serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
"serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
"helmEngineOrderStatus": "success",
"helmEngineOrderMessage": "Helm release termination was successful"
}
}
Interaction with Registry API
The Package Manager never holds Kubernetes credentials itself.
For every operation that requires a kubeconfig (deploy, activate, terminate), it sends a request to the Registry API via the Kafka channel get-package-registry-incoming-channel.
The Registry API retrieves the stored secret from Vault and replies on response-package-registry-outgoing-channel.
For chart parse operations involving private repositories, the same mechanism is used to retrieve repository credentials (username/password for Helm repos, OCI registries, or Git).
The type field inside the response message determines which internal handler the Package Manager routes the message to:
type value |
Routed to handler |
|---|---|
TypeParsing |
HandleParsing |
TypeDeployment |
HandleDeployment |
TypeActivation |
HandleActivation |
TypeTermination |
HandleTermination |
The two credential message structures are shown below.
Repository / registry credentials (used for chart fetch in the parse flow):
type RegistryURLMessage struct {
Type string `json:"type"`
KogitoServiceOrder kogito.RequestData[datamodel.ServiceOrder] `json:"kogitoServiceOrder"`
RepoCredentials RepoCredentials `json:"repoCredentials"`
}
type RepoCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
Image pull secret + kubeconfig (used for deploy, activate, and terminate flows):
type PullSecretMessage struct {
Type string `json:"type"`
HelmDeployRequest kogito.RequestData[datamodel.HelmDeployRequest] `json:"helmDeployRequest"`
ImagePullSecret ImagePullSecret `json:"imagePullSecret"`
}
type ImagePullSecret struct {
URL string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
}
Custom Scheduler Support
The Package Manager integrates with the Open Cluster Management (OCM) framework for multi-cluster deployments.
The scheduler integration is controlled by two fields in the ServiceOrder payload:
clusterManager: Set toocmto activate the OCM path.clusterMetadata: A Base64-encoded YAML manifest that describes workload-to-cluster mapping rules and the target scheduler namespace.
During the parse phase, the Package Manager decodes clusterMetadata, extracts the namespace field, and stores it as the Helm chart annotation sonata.engine/namespace.
This annotation is then read by the deploy, activate, and terminate handlers to determine the correct namespace of OCM scheduler, instead of defaulting to the serviceId.
An example clusterMetadata payload (after Base64 decoding) for the HypO scheduler:
apiVersion: scheduling.p2code.eu/v1alpha1
kind: P2CodeSchedulingManifest
metadata:
name: poc
namespace: p2code-scheduler-system
spec:
workloadAnnotations:
- name: httpd
annotations:
- p2code.filter.k8sdistribution=openshift
- name: nginx
annotations:
- p2code.filter.k8sdistribution=kubernetes
The namespace field (p2code-scheduler-system above) becomes the deployment namespace.
During termination with OCM, the namespace is intentionally kept alive because the OCM scheduler manages its lifecycle.
Adding Secrets to Vault
Secrets are stored in Vault and retrieved by the Registry API on behalf of the Package Manager.
Two categories exist:
Repository / registry credentials (for private Helm repos, OCI registries, or Git repos):
- Navigate to Secrets →
secretengine in the Vault UI. - Create a secret at the path matching the repository domain or URL:
- OCI registry:
registry.example.eu - Helm repo:
repo.example.com:8084/helm-repo - Git repo:
gitlab.com/api/v4/project - Set key-value pairs:
usernameandpassword.
Docker image pull secrets (for private container registries):
- Navigate to Secrets →
secretengine in the Vault UI. - Create a secret using the image pull secret name as the path (e.g.,
my-app-secret). - Set key-value pairs:
url,username, andpassword.