# CMEM Helm chart

This chart deploys the core CMEM consisting of the following components:

- Explore
- Dataintegration
- bootstrap done with eccenca-cmemc

Please see below on how to install, upgrade or uninstall the chart.

## Prerequisites

- Kubernetes 1.23+
- Helm 3+

## Cluster requirements

This section always depends on the load to be expected for the instance. Default setup requires ~ 8 GBs RAM
- Ressources: ~ 4CPU
  - Explore: 2,5 GB RAM
  - DataIntegration: 5GB RAM
  - InitContainer cmemc: 128M RAM
- Volumes
  - Explore:
    - tempFS/emptyDir 1GB /tmp
    - optional: tempFS/emptyDir <10M trust store for custom SSL certifcatates
  - DataIntegration:
    - Volume 5GB for DI datasets, uploaded data/files.
    - tempFS/emptyDir 500MB /tmp
    - tempFS/emptyDir 5GB cache
    - optional: tempFS/emptyDir 500M pythonPluginImageInstall
    - optional: tempFS/emptyDir <10M trust store for custom SSL certifcatates
  - Bootstrap:
    - optional: tempFS/emptyDir <10M trust store for custom SSL certifcatatest

## Releases and Compatibility matrix

- Helm chart and CMEM releases are released independent from each other, with different version schema (you can find the current version of the Helm chart under the field `key` in the file `Chart.yaml`)
- Any given release of this Helm chart supports a specific `<major>.<minor>` release line of CMEM. Feel free to change the default value for `dataNNNN.image.tag.` in a given Helm chart release to e.g. the latest stable release.
- The default value for `dataNNNN.image.tag.` in a given Helm chart release
  should be the latest stable release, of the latest supported `<major>.<minor>` line, at the
  time the Helm chart is released.
- If we drop support for older CMEM releases in our Helm chart, we increase the `<major>` version of that Helm chart.
- The `appVersion` of a Helm chart (see `Chart.yaml`) corresponds to the latest CMEM release this Helm chart supports (in the form of `<major>.<minor>`, e.g. `v22.2`)

| Helm Chart Release | Supported CMEM Releases | Default Release |
|--------------------|-------------------------|-----------------|
| 2.5.y              | 23.3 - 24.1             | 24.1.*          |
| 3.0.y              | 24.1 - 24.*             | 24.1.*          |
| 3.1.y              | 24.1 - 24.2             | 24.2.*          |
| 3.2.y              | 24.1 - 24.2             | 24.2.*          |
| 3.3.y              | 24.1 - 24.2             | 24.2.*          |
| 4.x.y              | 24.3 - 25.1             | 24.3.*          |
| 5.x.y              | 24.3 - 25.1             | 25.1.*          |
| 5.2.y              | 25.2.*                  | 25.2.*          |
| 5.3.y              | 25.2.*                  | 25.2.*          |
| 5.4.y              | 25.3 - *                | 25.3.*          |
| 5.5.y              | 25.3 - *                | 25.3.*          |
| 5.6.y              | 26.1 - *                | 26.1.*          |


## Branch and tag naming
```
Branches
  master          Points to latest release/tag
  develop         Long-lived dev
  feature/*       Short-lived dev
  attic/*         Cool ideas

Tags
  x.y.z           Helm chart releases
```

## Getting started

### Prerequisites

Understanding this helm chart requires you to have some basic understandings in the following technologies. There you also find instructions on how to install them. Soon we also provide a repository to give a basic local setup based on k3d a k3s in docker environment.

- Docker https://docs.docker.com/get-started/
- Kubernetes concepts https://kubernetes.io/docs/concepts/
- Helm https://helm.sh/docs/intro/quickstart/

As this was already mentionend this Repository only installs Corporate Memory without Keycloak and GraphDB. Here we asume you already have Graphdb and Keycloak deployed. Please see configurations further below.

- https://github.com/Ontotext-AD/graphdb-helm
- https://github.com/bitnami/charts/tree/main/bitnami/keycloak

#### Binaries
- Install `helm`: https://helm.sh/docs/intro/install/
- Install `kubectl`: https://kubernetes.io/docs/tasks/tools/install-kubectl/
- If deploying on K3D, download a static binary from
  https://github.com/k3d-io/k3d/releases (or use the script at https://k3d.io/
  to do the same).

## Architecture
 ![CMEM Helm Chart Architecture](images/cmem-helm-architecture.png)

## Quick Start

This section provides quick deployment scenarios for common environments.

### Quick Start: K3d (Local Development)

For local development with k3d:

```yaml
# quick-start-k3d.yaml
global:
  protocol: https
  hostname: cmem.docker.localhost
  cmemClientId: cmem-service-account
  cmemClientSecret: <your-secret>
  keycloakBaseUrl: https://keycloak.docker.localhost/auth
  keycloakIssuerUrl: https://keycloak.docker.localhost/auth/realms/cmem

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: cmem.docker.localhost

explore:
  companion:
    persistence:
      storageClassName: local-path  # k3d default storage class
  store:
    graphdb:
      enabled: true
      host: graphdb.graphdb
      repository: cmem
      user: provisioner
      password: <your-password>

dataintegration:
  persistence:
    storageClassName: local-path  # k3d default storage class

graphinsights:
  enabled: false  # Optional, enable if needed
  persistence:
    storageClassName: local-path
```

Deploy:
```bash
helm upgrade --install cmem . -n cmem --create-namespace -f quick-start-k3d.yaml
```

### Quick Start: Production

For production deployments with proper TLS:

```yaml
# quick-start-production.yaml
global:
  protocol: https
  hostname: cmem.example.com
  cmemClientId: cmem-service-account
  cmemClientSecret: <your-secret>
  keycloakBaseUrl: https://keycloak.example.com/auth
  keycloakIssuerUrl: https://keycloak.example.com/auth/realms/cmem
  license: cmem-license  # Reference to license secret

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: cmem.example.com
  tls:
    - secretName: cmem-tls-cert
      hosts:
        - cmem.example.com

explore:
  resources:
    limits:
      memory: 4Gi
    requests:
      memory: 4Gi
  store:
    graphdb:
      enabled: true
      host: graphdb.production.svc
      sslEnabled: false
      user: provisioner
      password: "changeme"

dataintegration:
  resources:
    limits:
      memory: 8Gi
    requests:
      memory: 8Gi
  persistence:
    storageClassName: fast-ssd  # Use production storage class
    storageSize: 20Gi
```

## Troubleshooting

### Common Issues

#### 1. Pods Stuck in Pending State

**Symptom**: Pods show `Pending` status indefinitely.

**Cause**: Usually PersistentVolumeClaim (PVC) cannot be bound due to:
- Missing or incorrect `storageClassName`
- No available PersistentVolumes
- Insufficient storage capacity

**Solution**:
```bash
# Check PVC status
kubectl get pvc -n cmem

# Check available storage classes
kubectl get storageclass

# Update values.yaml with correct storage class
# For k3d: use "local-path"
# For AWS: use "gp3" or "ebs-sc"
# For GCP: use "standard" or "pd-ssd"
```

#### 2. StorageClassName Immutability Error

**Symptom**: Helm upgrade fails with `PersistentVolumeClaim is invalid: spec: Forbidden: spec is immutable`

**Cause**: Trying to change `storageClassName` on existing PVC.

**Solution**:
```bash
# Option 1: Delete and recreate (DATA LOSS!)
kubectl delete namespace cmem
helm install cmem . -n cmem -f values.yaml

# Option 2: Fix values.yaml to match existing PVC
kubectl get pvc -n cmem -o yaml | grep storageClassName
# Update your values.yaml to match the existing storageClassName
```

#### 3. License Issues

**Symptom**: Application starts but features are limited or show license warnings.

**Cause**: Missing or incorrect license configuration.

**Solution**:
```bash
# Create license secret
kubectl create secret generic cmem-license \
  --from-file=license.asc=/path/to/license.asc \
  -n cmem

# Update values.yaml
global:
  license: cmem-license
```

#### 4. Keycloak Authentication Failures

**Symptom**: Unable to login, OAuth errors in logs.

**Cause**: Incorrect Keycloak configuration or network issues.

**Solution**:
```bash
# Verify Keycloak URLs are accessible from pods
kubectl exec -it explore-0 -n cmem -- curl -k <keycloak-url>

# Check Keycloak realm and client configuration
# Ensure cmem-service-account has Service Accounts Enabled
# Verify client secret matches values.yaml

# For k3d: Add extraHosts to resolve .docker.localhost domains
dataintegration:
  extraHosts:
    - ip: 172.18.0.X  # Cluster endpoint IP
      hostnames:
        - keycloak.docker.localhost
```

#### 5. Image Pull Errors

**Symptom**: `ImagePullBackOff` or `ErrImagePull` status.

**Cause**: Missing or incorrect Docker registry credentials.

**Solution**:
```bash
# Recreate docker registry secret
kubectl create secret docker-registry eccenca-docker-registry-credentials \
  --docker-server=https://docker-registry.eccenca.com \
  --docker-username=<your-username> \
  --docker-password=<your-password> \
  -n cmem

# Or disable auto-creation and use manual secret
imageCredentials:
  enabled: false
```

### Debug Commands

```bash
# Check pod status and events
kubectl get pods -n cmem
kubectl describe pod <pod-name> -n cmem

# View pod logs
kubectl logs -f <pod-name> -n cmem

# Check PVC status
kubectl get pvc -n cmem
kubectl describe pvc <pvc-name> -n cmem

# Test connectivity from pod
kubectl exec -it <pod-name> -n cmem -- /bin/bash

# View Helm release status
helm list -n cmem
helm status cmem -n cmem

# Check differences before upgrade
helm diff upgrade cmem . -n cmem -f values.yaml
```

## Installation


This guide provides instructions on how to install the chart using `kubectl` and `helm`.
You need to have a keycloak instance and a supported graph database installed.

### 1. Download the chart or use our helm repository or clone the repository

```console
wget https://releases.eccenca.com/cmem-helm/latest.tgz
tar -xzf latest.tgz
cd cmem
```

```console
helm repo add --force-update cmem-helm https://helm.eccenca.com
helm repo update cmem-helm
```

```console
# this requires gitlab.eccenca.com access
git clone https://gitlab.eccenca.com/cmem/cmem-helm.git
cd cmem-helm
```

### 2. Create a namespace

It is recommended to install CMEM in its own namespace.

```console
kubectl create namespace <your-namespace>
```
Replace `<your-namespace>` with the desired namespace (e.g., `cmem`).

### 3. Create Docker registry credentials

To pull the CMEM images, you need to provide credentials to your Docker registry.

```console
kubectl create secret docker-registry eccenca-docker-registry-credentials \
  --docker-server=https://docker-registry.eccenca.com \
  --docker-username=<your-docker-username> \
  --docker-password=<your-docker-password> \
  -n <your-namespace>
```
Replace the placeholders with the provided registry details and credentials.

### 3b. (optional) Create cmem license secret

By default, Corporate Memory is subject to the eccenca free Personal, Evaluation and Development License Agreement (PEDAL), a license intended for non-commercial usage.

If you have a dedicated license file, create a secret with a `license.asc` data entry:

```console
kubectl create secret generic cmem-license \
  --from-file license.asc
  -n <your-namespace>
```

Then, add the secret name to your `values.yaml` file for the key `global.license`.

For more background on license, see also: https://documentation.eccenca.com/latest/deploy-and-configure/configuration/dataplatform/application-full/

### 4. Configure your deployment

Copy the `values.sample.yaml` to a new file, for example `my-values.yaml`.

```console
cp values.sample.yaml my-values.yaml
```

Edit `my-values.yaml` and adjust the configuration to your needs.
At a minimum, you will need to configure the hostname, and connection details for your Ingress or Route, Keycloak and GraphDB.

```yaml
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: <cluster-issuer-if-present>
  hosts:
    - host: "<your-cmem-hostname>"
      paths:
        - path: /
          pathType: Prefix
          serviceName: explore
          servicePort: 8080
        - path: /dataintegration
          pathType: Prefix
          serviceName: dataintegration
          servicePort: 8080
        - path: /graphinsights
          pathType: Prefix
          serviceName: graphinsights
          servicePort: 8080
  tls:
    - hosts:
         - "<your-cmem-hostname>"
      secretName:  cmem-ingress-cert

global:
  protocol: "https"
  cmemClientId: cmem-service-account
  cmemClientSecret: changeme
  graphinsightsClientId: graph-insights-service-account
  graphinsightsClientSecret: changeme

  hostname: "<your-hostname>"
  keycloakBaseUrl: https://<your-keycloak-hostname>/auth/'
  keycloakIssuerUrl: https://<your-keycloak-hostname>/auth/realms/cmem'
  oauthClientId: cmem
  oauthClientIdGraphInsights: cmem
  # If you specified customCACerts, an initContainer is added to DI and EXPLORE to append your custom CA to the system-wide TrustStore.
  # Here you can optionally specify resource requests and limits for that initContainer.
  customCACerts: {}

  # (optional if 3b was created)
  # license: cmem-license

explore:
  store:
    graphdb:
      enabled: true
      repository: cmem
      user: provisioner
      password: "iHaveSuperpowers"
      host: "<your-graphdb-hostname>"
      sslEnabled: false

```

### 4.1 Configure LLM Provider (explore)


In Explore we use Spring AI to configure an LLM provider. For full reference
see [https://docs.spring.io/spring-ai/reference/api/chat/comparison.html](https://docs.spring.io/spring-ai/reference/api/chat/comparison.html).
For the most useful models and provider we added example configurations in `configuration-files/application-llm-*.yml`.
To enable the companion set `.Values.explore.companion.enabled`.
You then have to configure the Spring AI module in `.Values.explore.companion.config`.
See the examples for that.

### 4.2 LLM Provider Setup (DataIntegration)

For DataIntegrations Mapping Creator LLM support you would need to enable this `.Values.dataintegration.mappingCreator.enabled`
Further configuration for this can be found in `configuration-files/dataintegration.conf` section `com.eccenca.di.assistant`.

``` yaml
.Values.dataintegration.mappingCreator:
  enable: false
  AiApiKey: "changeme"
  AiOrgId : ""
  AiModel: "openai/gpt-5-mini"
  AiCoreUrl: "https://openrouter.ai/api/v1"
  AiReasoningEffort: "low"
  AiLogQueries: false
```

### 5. Install the chart

Use `helm` to deploy the chart.

```console
# In case you have the chart or repostiory locally available
helm upgrade --install <release-name> .
  --namespace <your-namespace> \
  -f my-values.yaml

# or use our helm repository
helm upgrade --install <release-name> cmem-helm/cmem
  --namespace <your-namespace> \
  -f my-values.yaml
```

- `<release-name>`: A name for this release (e.g., `cmem`).
- `<your-namespace>`: The namespace created in step 2.

This command will install the chart in the specified namespace using your custom configuration.

_See [configuration](#configuration) below for more details on available options._

_See [helm install](https://helm.sh/docs/helm/helm_install/) for command documentation._

### 6. Verify the installation

After the installation is complete, you can check the status of the pods:

```console
kubectl get pods -n <your-namespace>
```

You can also check the rollout status of the statefulsets:

```console
kubectl rollout status statefulset/explore -n <your-namespace>
kubectl rollout status statefulset/dataintegration -n <your-namespace>
```

## Uninstall chart

```console
helm uninstall -n [RELEASE_NAMESPACE] [RELEASE_NAME]
```

This removes all the Kubernetes components associated with the chart and deletes the release.

_See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation._

## Preview Helm changes

The [helm-diff](https://github.com/databus23/helm-diff) plugin gives us the possibility to preview what a `helm upgrade` would change.

Install it via `helm plugin install https://github.com/databus23/helm-diff`, then you can run the following prior to an install or upgrade command:

```console
# Helm (requires helm-diff plugin)
helm diff upgrade --install -n [RELEASE_NAMESPACE] [RELEASE_NAME] [-f values.yaml] .
```

## Render Helm templates

To render plain Kubernetes manifests from the Helm chart, run:

```console
helm template -n [RELEASE_NAMESPACE] [RELEASE_NAME] .
```

## Configuration

See [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing). To see all configurable options with detailed comments:

```console
helm show values .
```
### Configuring Graphdb as backend

Please visit https://github.com/Ontotext-AD/graphdb-helm to get familiar with Ontotext GraphDB. Here we show some

```console
helm repo add --force-update ontotext http://maven.ontotext.com/repository/helm-public"
helm repo update ontotext"

kubectl create namespace graphdb || true"
kubectl -n graphdb create secret generic graphdb-license --from-file graphdb/graphdb.license"
helm upgrade \
    -i \
    -n graphdb \
    --create-namespace
    graphdb GRAPHDB-CHART-OR-PATH
    -f your-graphdb-values.yaml
    --set graphdb.node.license='graphdb-license'"
kubectl rollout status statefulset -n graphdb graphdb-node"
```

You will need to configure those values to set up GraphDB with Corporate Memory.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| explore.store.graphdb.enabled | bool | `false` | enable this to use GraphDB as backend |
| explore.store.graphdb.repository | string | `cmem` | the repository Corporate Memory stores its data inside GraphDB |
| explore.store.graphdb.user | string | `provisioner` | the GraphDB user used by explore |
| explore.store.graphdb.password | string | `` | the GraphDB users password used by explore |
| explore.store.graphdb.host | string | `` | the GraphDB hostname of the instance (not the URL) |
| explore.store.graphdb.port | string | `7200` | the GraphDB port of the instance or service |
| explore.store.graphdb.sslEnabled | bool | `false` | in case the connections is encrypted (be aware for potential pitfalls here) |


### Configuring Keycloak as authentication provider


```console
helm repo add --force-update bitnami https://charts.bitnami.com/bitnami"
helm repo update bitnami"
kubectl create namespace keycloak --dry-run=client -o yaml | kubectl apply -f -"
# in case you have a working postgres dump
kubectl -n keycloak delete ConfigMap keycloak-postgresql-init-scripts || true "
# in case you have a working postgres dump
cat keycloak/keycloak-postgresql-init-scripts.template.json
          | jq -M --rawfile keycloak_db_sql keycloak/keycloak_db.sql '.data[\"keycloak_db.sql\"] = $keycloak_db_sql'
          | kubectl -n keycloak create -f -"
      kubectl -n keycloak apply -f keycloak/keycloak-secret.yaml"
      kubectl -n keycloak apply -f keycloak/keycloak-postgres-secret.yaml"
      kubectl -n keycloak delete --ignore-not-found=true secret eccenca-docker-registry-credentials"
      kubectl -n keycloak apply -f eccenca-docker-registry-credentials.yaml"
      - defer: rm -f eccenca-docker-registry-credentials.yaml
helm upgrade
          -i
          -n keycloak
          --create-namespace
          keycloak bitnami/keycloak
          -f keycloak/keycloak-values.yaml
          --set ingress.hostname={{ .KEYCLOAK_HOSTNAME }}
          --set initContainers[0].image={{ .KEYCLOAK_THEME_VERSION }}"
      - "cat keycloak/keycloak_separate_ingress.yml
          | sed s/KEYCLOAK_HOSTNAME/{{ .KEYCLOAK_HOSTNAME }}/g
          | sed s/CLUSTER_ISSUER_NAME/{{ .CLUSTER_ISSUER_NAME }}/g
          | kubectl apply -f -"
kubectl rollout status statefulset -n keycloak keycloak-postgresql"
kubectl rollout status statefulset -n keycloak keycloak"
```


You will need to configure those values to set up Keycloak with Corporate Memory.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| global.cmemClientId | string | `false` | cmem client ID and secret; see also: https://documentation.eccenca.com/24.1/deploy-and-configure/configuration/keycloak/ |
| global.cmemClientSecret | string | `cmem` |  |
| global.keycloakBaseUrl | string | `cmem` | keycloak base url (e.g. https://cmem.example.com/auth) |
| global.keycloakIssuerUrl | string | `cmem` | keycloak cmem realm url (e.g. https://cmem.example.com/auth/realms/cmem)|
| global.oauthClientId | string | `cmem` | keycloak oauth client id (used for explore connection and DataIntegration)|


To configure the installation, please specify additional helm value files via `-f values.yaml`.

We provide a (pre-filled) `values.sample.yaml` file with a minimal set of configuration parameters you would need to provide.
