Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#171 - Allow setting up read only replicas #174

Merged
merged 24 commits into from
Oct 9, 2024

Conversation

davidfrickert
Copy link
Contributor

What this PR does / why we need it:

  • necessary changes to support creating a statefulset comprising of read only replicas

Pre-submission checklist:

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you updated the readme?
  • Is this PR backward compatible? If it is not backward compatible, please discuss open a ticket first

Need to do reduce duplication as of right now there is huge duplication in statefulset, services, etc.

- this change is needed to ensure that generic configmap can be used by both writable and readonly replicas. then a schemas configmap for writable replicas and one for readonly can be created without much duplication.
- allows for example to only enable SSL port on service but allow both ports on internal headless service
@jp-gouin
Copy link
Owner

First of all thank you for your great contribution !

can you add a test in .github/workflows/ci-others.yaml to test that in read only mode you can read but not write ? Let me know if you need help

@davidfrickert
Copy link
Contributor Author

First of all thank you for your great contribution !

can you add a test in .github/workflows/ci-others.yaml to test that in read only mode you can read but not write ? Let me know if you need help

No worries. Great suggestion, will do.

@jp-gouin
Copy link
Owner

jp-gouin commented Jun 24, 2024

I tried it and I found out that if you are using olcReadOnly: TRUE you are locking the base and the replication can't work.
Did you have the same issue ?

If I want to still have the replication working, I think it's best to use a strict access control list such as :

readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    add: olcAccess
    olcAccess: to *
      by dn.exact="{{ include "global.bindDN" . }}" write
      by * read

Using this I ensure that only admin user is allowed to write , others can only read

@davidfrickert
Copy link
Contributor Author

I tried it and I found out that if you are using olcReadOnly: TRUE you are locking the base and the replication can't work.
Did you have the same issue ?

If I want to still have the replication working, I think it's best to use a strict access control list such as :

readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    add: olcAccess
    olcAccess: to *
      by dn.exact="{{ include "global.bindDN" . }}" write
      by * read

Using this I ensure that only admin user is allowed to write , others can only read

Replication is working fine in my case but I will check if I forgot to commit something. From docs that I read olcReadOnly should not break replication

Copy link
Owner

Choose a reason for hiding this comment

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

In the init-schema init container we should remove the first half since the read only will necessarily replicate against the other one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -193,6 +193,8 @@ spec:
{{- end }}
- configMapRef:
name: {{ template "openldap.fullname" . }}-env
- configMapRef:
Copy link
Owner

Choose a reason for hiding this comment

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

I think it's best to add a env var and drop the configmap-env-schemas.yaml file

- name: LDAP_EXTRA_SCHEMAS
  value: {{ print "cosine,inetorgperson,nis," (include "openldap.schemaFiles" .)}}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

{{- if .Values.containerSecurityContext.enabled }}
securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
{{- end }}
env:
Copy link
Owner

Choose a reason for hiding this comment

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

I think it's best to add a env var and drop the configmap-env-readonly-schemas.yaml file

- name: LDAP_EXTRA_SCHEMAS
  value: {{ print "cosine,inetorgperson,nis," (include "openldap.schemaFiles" .) ",readonly"}}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

- sh
- -c
- |
host=$(hostname)
Copy link
Owner

@jp-gouin jp-gouin Jun 24, 2024

Choose a reason for hiding this comment

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

can be simplified to

command:
  - sh
  - -c
  - |
    cp -p -f /cm-schemas-acls/*.ldif /custom_config/
    echo "let the replication takes care of everything :)"
  {{- if .Values.global.existingSecret }}
    sed -i -e "s/%%CONFIG_PASSWORD%%/${LDAP_CONFIG_ADMIN_PASSWORD}/g" /custom_config/*
    sed -i -e "s/%%ADMIN_PASSWORD%%/${LDAP_ADMIN_PASSWORD}/g" /custom_config/*
  {{- end }}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@jp-gouin
Copy link
Owner

If I leave the default

  readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    olcReadonly: TRUE

I can see the following logs :

openldap-readonly-0 openldap-stack-ha 6679713c.015537dd 0x7fe06d9896c0 conn=1011 op=1 ADD dn="olcDatabase={2}mdb,cn=config"
openldap-readonly-0 openldap-stack-ha 6679713c.0156fc39 0x7fe06d9896c0 is_entry_objectclass("olcDatabase={2}mdb,cn=config", "2.5.17.0") no objectClass attribute
openldap-readonly-0 openldap-stack-ha 6679713c.0157cab7 0x7fe06d9896c0 No objectClass for entry (olcDatabase={2}mdb,cn=config)
openldap-readonly-0 openldap-stack-ha 6679713c.0158bbfe 0x7fe06d9896c0 conn=1011 op=1 RESULT tag=105 err=65 qtime=0.000015 etime=0.000271 text=no objectClass attribute
openldap-readonly-0 openldap-stack-ha ldap_add: Object class violation (65)
openldap-readonly-0 openldap-stack-ha   additional info: no objectClass attribute

Openldap complain about the ldif not containing the objectClass attribute

If I change the ldif to

  readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changeType: modify
    replace: olcReadOnly
    olcReadOnly: TRUE

It's correctly added but the replication seems broken :

nldap-readonly-0 openldap-stack-ha 66797204.02542172 0x7f07c23d66c0 conn=1013 op=1 ADD dn="dc=example,dc=org"
openldap-readonly-0 openldap-stack-ha 66797204.0254e49d 0x7f07c23d66c0 conn=1013 op=1 RESULT tag=105 err=53 qtime=0.000009 etime=0.000075 text=operation restricted
openldap-readonly-0 openldap-stack-ha ldap_add: Server is unwilling to perform (53)
openldap-readonly-0 openldap-stack-ha   additional info: operation restricted
openldap-readonly-0 openldap-stack-ha 66797204.0256a1b9 0x7f07c2bd76c0 conn=1013 op=2 UNBIND
openldap-readonly-0 openldap-stack-ha adding new entry "dc=example,dc=org"

What is your output when you run
LDAPTLS_REQCERT=never ldapsearch -o nettimeout=20 -x -D 'cn=admin,dc=example,dc=org' -w Not@SecurePassw0rd -H ldaps://localhost:8636 -b 'dc=example,dc=org' (given ldaps://localhost:8636 is your read only server) ?

@davidfrickert
Copy link
Contributor Author

davidfrickert commented Jun 24, 2024

Sure, here:

dfrickert@VD011936:~$ sudo LDAPTLS_REQCERT=hard LDAPTLS_CACERT=/etc/ssl/certs/TestLDAP_CA.pem ldapsearch -x -H ldaps://127.0.0.1.sslip.io:636 -D 'cn=admin,dc=example,dc=com' -W -b "dc=example,dc=com"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=example,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# example.com
dn: dc=example,dc=com
objectClass: dcObject
objectClass: organization
dc: example
o: example

# users, example.com
dn: ou=users,dc=example,dc=com
objectClass: organizationalUnit
ou: users

# user01, users, example.com
dn: cn=user01,ou=users,dc=example,dc=com
cn: User1
cn: user01
sn: Bar1
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
userPassword:: Yml0bmFtaTE=
uid: user01
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/user01

# user02, users, example.com
dn: cn=user02,ou=users,dc=example,dc=com
cn: User2
cn: user02
sn: Bar2
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
userPassword:: Yml0bmFtaTI=
uid: user02
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/user02

# readers, users, example.com
dn: cn=readers,ou=users,dc=example,dc=com
cn: readers
objectClass: groupOfNames
member: cn=user01,ou=users,dc=example,dc=com
member: cn=user02,ou=users,dc=example,dc=com

# testuser, users, example.com
dn: uid=testuser,ou=users,dc=example,dc=com
uid: testuser
objectClass: inetOrgPerson
objectClass: organizationalPerson
sn:: IA==
cn:: IA==

# search result
search: 2
result: 0 Success

# numResponses: 7
# numEntries: 6

This query is going to the openldap-readonly service and logs of readonly pod confirm that:

66797c87.0c7bad70 0x7fa0bedeb6c0 conn=61360 op=1 SRCH base="dc=example,dc=com" scope=2 deref=0 filter="(objectClass=*)"
66797c87.0c916931 0x7fa0bedeb6c0 conn=61360 op=1 SEARCH RESULT tag=101 err=0 qtime=0.000084 etime=0.001529 nentries=6 text=

I also have no replication errors, have created multiple users (such as testuser in above query).

kubectl get svc -n keycloak-iam
NAME                                TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
postgresql-ha-postgresql-headless   ClusterIP      None            <none>        5432/TCP            4d4h
postgresql-ha-pgpool                ClusterIP      10.43.159.24    <none>        5432/TCP            4d4h
postgresql-ha-postgresql            ClusterIP      10.43.107.84    <none>        5432/TCP            4d4h
keycloak-operator                   ClusterIP      10.43.73.14     <none>        80/TCP              4d3h
keycloak-idp-discovery              ClusterIP      None            <none>        7800/TCP            4d3h
keycloak-idp-service                ClusterIP      10.43.74.135    <none>        8080/TCP,8443/TCP   4d3h
openldap-headless-readonly          ClusterIP      None            <none>        389/TCP,636/TCP     3d1h
openldap-headless                   ClusterIP      None            <none>        389/TCP,636/TCP     3d1h
openldap                            ClusterIP      10.43.169.243   <none>        389/TCP,636/TCP     3d1h
openldap-phpldapadmin               ClusterIP      10.43.176.98    <none>        80/TCP              3d1h
openldap-readonly                   LoadBalancer   10.43.242.91    127.0.0.1     636:30144/TCP       3d1h

@davidfrickert
Copy link
Contributor Author

openldap-readonly-0 openldap-stack-ha additional info: no objectClass attribute

i had this issue as well, in LDAP_EXTRA_SCHEMAS, readonly needs to be the last one in this env var, which is what the chart should currently do

@davidfrickert
Copy link
Contributor Author

i will try to write a github test to see if it leads to your issue as well, but my values are as follows:

customAcls: |-
  dn: olcDatabase={2}mdb,cn=config
  changetype: modify
  replace: olcAccess
  olcAccess: {0}to *
    by dn.exact=gidNumber=0+uidNumber=1001,cn=peercred,cn=external,cn=auth manage
    by * break
  olcAccess: {1}to attrs=userPassword,shadowLastChange
    by self write
    by anonymous auth by * none
  olcAccess: {2}to *
    by self read
    by * search
env:
  BITNAMI_DEBUG: "true"
  LDAP_ALLOW_ANON_BINDING: "no"
  LDAP_ENABLE_TLS: "yes"
  LDAP_LOGLEVEL: "256"
  LDAP_SKIP_DEFAULT_TREE: "no"
  LDAP_TLS_ENFORCE: "false"
  LDAPTLS_REQCERT: never
global:
  existingSecret: openldap-secrets
  imageRegistry: VD011936.example.com:5000
  ldapDomain: example.com
image:
  repository: <private-repo>/jpgouin/openldap
  tag: 2.6.7-fix
initSchema:
  image:
    repository: <private-repo>/debian
    tag: latest
initTLSSecret:
  image:
    repository: <private-repo>/alpine/openssl
    tag: latest
  secret: openldap-tls-secret
  tls_enabled: true
ltb-passwd:
  enabled: false
persistence:
  accessModes:
  - ReadWriteOnce
  enabled: true
  size: 1Gi
phpldapadmin:
  enabled: true
  image:
    repository: <private-repo>/osixia/phpldapadmin
    tag: 0.9.0
  ingress:
    enabled: true
    hosts:
    - phpldapadmin.127.0.0.1.sslip.io
    path: /
    pathType: Prefix
    tls:
    - hosts:
      - phpldapadmin.127.0.0.1.sslip.io
      secretName: phpldapadmin-tls-secret
replicaCount: 3
readOnlyReplicaCount: 1

replication:
  clusterName: cluster.local
  enabled: true
  interval: "00:00:00:10"
  retry: 60
  starttls: critical
  timeout: 1
  tls_reqcert: never

@davidfrickert
Copy link
Contributor Author

Also, might or might not be relevant, I noticed that there is an issue on ACLs in cluster mode, so i do run:

kubectl exec -n {{ iam_namespace }} openldap-0 -- bash -c "/opt/bitnami/openldap/bin/ldapmodify -Y EXTERNAL -H ldapi:/// -f /opt/bitnami/openldap/etc/schema/acls.ldif

manually, once the main statefulset cluster is healthy.

- removed env cm for schemas and instead just use env in each SS
- removed part of readonly init container that only applies to master cluster
@davidfrickert
Copy link
Contributor Author

If you can give me permissions to run workflows ad-hoc that would be nice! @jp-gouin (not fully sure how that works)

@jp-gouin
Copy link
Owner

@davidfrickert manual approval is required for first time contributor

Anyway I recommend you use act to test your workflow locally before commit . This will save you quite some time

sslLdapPortNodePort: 30636
type: NodePort
serviceReadOnly:
ldapPortNodePort: 31389
Copy link
Owner

Choose a reason for hiding this comment

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

You need to update .bin/kind-conf.yml to include the 2 new ports :

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 8080
    protocol: TCP
  - containerPort: 443
    hostPort: 8444
    protocol: TCP
  - containerPort: 30636
    hostPort: 30636
  - containerPort: 30389
    hostPort: 30389
  - containerPort: 31389
    hostPort: 31389
  - containerPort: 31636
    hostPort: 31636
- role: worker
- role: worker

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, thanks for the tip

@davidfrickert
Copy link
Contributor Author

davidfrickert commented Jun 25, 2024

I tried it and I found out that if you are using olcReadOnly: TRUE you are locking the base and the replication can't work. Did you have the same issue ?

If I want to still have the replication working, I think it's best to use a strict access control list such as :

readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    add: olcAccess
    olcAccess: to *
      by dn.exact="{{ include "global.bindDN" . }}" write
      by * read

Using this I ensure that only admin user is allowed to write , others can only read

just checked this and you're right, the pod stays healthy after crashing once but the read only setting is not applied, am checking on alternatives
will try your suggestion

EDIT:

I don't think the olcAccess way works as this gets replicated onto the rest of the cluster

@davidfrickert
Copy link
Contributor Author

okay, RO replica with ACL actually seems to work.
But it seems it can't be fully read-only as the admin account still can write onto it.
Also, if any ACLs are applied to main cluster, they are sync'd to the replica and read-only capabilities are lost, which is unfortunate.

@davidfrickert
Copy link
Contributor Author

another update, i finally found out how to make it truly read only.
the read only replica cannot have the "olcMirrorMode" set to TRUE. and it needs to have olcUpdateref set. Then it will reject all write requests. will modify PR

@davidfrickert
Copy link
Contributor Author

davidfrickert commented Jun 26, 2024

any ideas on how to stop olcMirrorMode/olcMultiProvider to be replicated from master replicas to read only replica @jp-gouin ?
what i have right now locally to make it work is run manually (olcMultiProvider is new name of olcMirrorMode):

    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    delete: olcMultiProvider

    dn: olcDatabase={0}config,cn=config
    changetype: modify
    delete: olcMultiProvider

but i'd prefer if this attribute is not replicated so i dont have to run manual stuff.
experimented adding exattrs=olcMirrorMode,olcMultiProvider to olcSyncrepl but no luck

@jp-gouin
Copy link
Owner

How about changing the type=refreshAndPersist to type=refreshOnly for read only replica ?
Can't try it now , you can give it a shot

@davidfrickert
Copy link
Contributor Author

How about changing the type=refreshAndPersist to type=refreshOnly for read only replica ? Can't try it now , you can give it a shot

will try, thanks

@davidfrickert
Copy link
Contributor Author

davidfrickert commented Jun 26, 2024

How about changing the type=refreshAndPersist to type=refreshOnly for read only replica ? Can't try it now , you can give it a shot

I think this might help a bit in another issue that i was facing that was deleting olcMirrorMode from "read only replica" would also delete it from main cluster.
But unfortunately it does not stop this attribute from being sync'd from main cluster to RO replica, thus still requiring the manual delete of the attribute from the replica's databases.

@davidfrickert
Copy link
Contributor Author

davidfrickert commented Jun 26, 2024

latest commits should make tests work for readonly (tested locally)
although i now have changed default repl to refreshOnly so should perhaps work on a new commit to split that up such that only the readonly replica works on refreshOnly mode.
still don't like that a manual exec is needed to remove mirror mode but atm can't see a cleaner solution

edit: well, putting normal nodes in refreshAndPersist and read only in refreshOnly doesn't work as that is also sync'd so eventually replica also goes into refreshAndPersist mode

@jp-gouin
Copy link
Owner

jp-gouin commented Jun 30, 2024

So I got a pretty decent results with the following changes :

The master cluster is not aware of the readonly nodes, they keep doing the replication as expected.
readonly nodes are aware of the master cluster and do the base replication. This is done by applying only the brep.ldif file to all readonly nodes. And no change on the _helpers.tpl from the main branch.

The cn=config is not replicated from master to readonly , so additional schemas should be applied on the first run , or applied manually to others readonly .

Finally, readonly nodes only allows write from the admin and deny all using the acl of the configmap-readonly.yaml .

configmap-readonly.yaml:

{{- if (gt (.Values.readOnlyReplicaCount | int) 0) }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "openldap.fullname" . }}-readonly
  labels:
    app: {{ template "openldap.name" . }}
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
{{- if .Values.extraLabels }}
{{ toYaml .Values.extraLabels | indent 4 }}
{{- end }}
data:
  readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    add: olcAccess
    olcAccess: to *
      by dn.exact="{{ include "global.bindDN" . }}" write
      by * read
{{- end }}

No change of the _helpers.tpl from the main branch :

{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "openldap.name" -}}
{{- default .Release.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "openldap.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Release.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "openldap.chart" -}}
{{- printf "%s-%s" .Release.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create the name of the service account to use
*/}}
{{- define "openldap.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
    {{ default (printf "%s-foo" (include "common.names.fullname" .)) .Values.serviceAccount.name }}
{{- else -}}
    {{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

{{/*
Generate chart secret name
*/}}
{{- define "openldap.secretName" -}}
{{ default (include "openldap.fullname" .) .Values.global.existingSecret }}
{{- end -}}

{{/*
Generate olcServerID list
*/}}
{{- define "olcServerIDs" }}
{{- $name := (include "openldap.fullname" .) }}
{{- $namespace := .Release.Namespace }}
{{- $cluster := .Values.replication.clusterName }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcServerID: {{ $index1 }} ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389
  {{- end -}}
{{- end -}}

{{/*
Generate olcSyncRepl list
*/}}
{{- define "olcSyncRepls" -}}
{{- $name := (include "openldap.fullname" .) }}
{{- $namespace := .Release.Namespace }}
{{- $bindDNUser := .Values.global.adminUser }}
{{- $cluster := .Values.replication.clusterName }}
{{- $configPassword :=  ternary .Values.global.configPassword "%%CONFIG_PASSWORD%%" (empty .Values.global.existingSecret) }}
{{- $retry := .Values.replication.retry }}
{{- $timeout := .Values.replication.timeout }}
{{- $starttls := .Values.replication.starttls }}
{{- $tls_reqcert := .Values.replication.tls_reqcert }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcSyncRepl: rid=00{{ $index1 }} provider=ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389 binddn="cn={{ $bindDNUser }},cn=config" bindmethod=simple credentials={{ $configPassword }} searchbase="cn=config" type=refreshAndPersist retry="{{ $retry }} +" timeout={{ $timeout }} starttls={{ $starttls }} tls_reqcert={{ $tls_reqcert }}
  {{- end -}}
{{- end -}}

{{/*
Generate olcSyncRepl list
*/}}
{{- define "olcSyncRepls2" -}}
{{- $name := (include "openldap.fullname" .) }}
{{- $domain := (include "global.baseDomain" .) }}
{{- $bindDNUser := .Values.global.adminUser }} 
{{- $namespace := .Release.Namespace }}
{{- $cluster := .Values.replication.clusterName }}
{{- $adminPassword := ternary .Values.global.adminPassword "%%ADMIN_PASSWORD%%" (empty .Values.global.existingSecret) }}
{{- $retry := .Values.replication.retry }}
{{- $timeout := .Values.replication.timeout }}
{{- $starttls := .Values.replication.starttls }}
{{- $tls_reqcert := .Values.replication.tls_reqcert }}
{{- $interval := .Values.replication.interval }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcSyncrepl:
      rid=10{{ $index1 }}
      provider=ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389
      binddn={{ printf "cn=%s,%s" $bindDNUser $domain }}
      bindmethod=simple
      credentials={{ $adminPassword }}
      searchbase={{ $domain }}
      type=refreshAndPersist
      interval={{ $interval }}
      network-timeout=0
      retry="{{ $retry }} +"
      timeout={{ $timeout }}
      starttls={{ $starttls }}
      tls_reqcert={{ $tls_reqcert }}
  {{- end -}}
{{- end -}}

{{/*
Renders a value that contains template.
Usage:
{{ include "openldap.tplValue" ( dict "value" .Values.path.to.the.Value "context" $) }}
*/}}
{{- define "openldap.tplValue" -}}
    {{- if typeIs "string" .value }}
        {{- tpl .value .context }}
    {{- else }}
        {{- tpl (.value | toYaml) .context }}
    {{- end }}
{{- end -}}

{{/*
Return the proper Openldap image name
*/}}
{{- define "openldap.image" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Docker Image Registry Secret Names
*/}}
{{- define "openldap.imagePullSecrets" -}}
{{ include "common.images.pullSecrets" (dict "images" (list .Values.image ) "global" .Values.global) }}
{{- end -}}


{{/*
Return the proper Openldap init container image name
*/}}
{{- define "openldap.initTLSSecretImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.initTLSSecret.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Openldap init container image name
*/}}
{{- define "openldap.initSchemaImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.initSchema.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Openldap volume permissions init container image name
*/}}
{{- define "openldap.volumePermissionsImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) -}}
{{- end -}}


{{/*
Return the list of builtin schema files to mount
Cannot return list => return string comma separated
*/}}
{{- define "openldap.builtinSchemaFiles" -}}
  {{- $schemas := "" -}}
  {{- if .Values.replication.enabled -}}
    {{- $schemas = "syncprov,serverid,csyncprov,rep,bsyncprov,brep,acls" -}}
  {{- else -}}
    {{- $schemas = "acls" -}}
  {{- end -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the list of custom schema files to use
Cannot return list => return string comma separated
*/}}
{{- define "openldap.customSchemaFiles" -}}
  {{- $schemas := "" -}}
  {{- $schemas := ((join "," (.Values.customSchemaFiles | keys | sortAlpha))  | replace ".ldif" "") -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the list of all schema files to use
Cannot return list => return string comma separated
*/}}
{{- define "openldap.schemaFiles" -}}
  {{- $schemas := (include "openldap.builtinSchemaFiles" .) -}}
  {{- $custom_schemas := (include "openldap.customSchemaFiles" .) -}}
  {{- if gt (len $custom_schemas) 0 -}}
    {{- $schemas = print $schemas "," $custom_schemas  -}}
  {{- end -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the proper base domain
*/}}
{{- define "global.baseDomain" -}}
{{- $bd := include "tmp.baseDomain" .}}
{{- printf "%s" $bd | trimSuffix "," -}}
{{- end }}

{{/*
tmp method to iterate through the ldapDomain
*/}}
{{- define "tmp.baseDomain" -}}
{{- if regexMatch ".*=.*" .Values.global.ldapDomain }}
{{- printf "%s" .Values.global.ldapDomain }}
{{- else }}
{{- $parts := split "." .Values.global.ldapDomain }}
  {{- range $index, $part := $parts }}
  {{- $index1 := $index | add 1 -}}
dc={{ $part }},
  {{- end}}
  {{- end -}}
{{- end -}}

{{/*
Return the server name
*/}}
{{- define "global.server" -}}
{{- printf "%s.%s" .Release.Name .Release.Namespace  -}}
{{- end -}}

{{/*
Return the bdmin indDN
*/}}
{{- define "global.bindDN" -}}
{{- printf "cn=%s,%s" .Values.global.adminUser (include "global.baseDomain" .) -}}
{{- end -}}

{{/*
Return the ldaps port
*/}}
{{- define "global.ldapsPort" -}}
{{- printf "%d" .Values.global.sslLdapPort  -}}
{{- end -}}

{{/*
Return the ldap port
*/}}
{{- define "global.ldapPort" -}}
{{- printf "%d" .Values.global.ldapPort  -}}
{{- end -}}

statefulset-readonly.yaml :

{{- if (gt (.Values.readOnlyReplicaCount | int) 0) }}
apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
  name:  {{ template "openldap.fullname" . }}-readonly
  labels: {{- include "common.labels.standard" . | nindent 4 }}
    app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  {{- if .Values.commonAnnotations }}
  annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
  {{- end }}
spec:
  replicas: {{ .Values.readOnlyReplicaCount }}
  selector:
    matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }}
      app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
  serviceName: {{ template "openldap.fullname" . }}-headless-readonly
  {{- if .Values.updateStrategy }}
  updateStrategy:
{{ toYaml .Values.updateStrategy | nindent 4 }}
  {{- end }}
  template:
    metadata:
      annotations:
        {{- if .Values.podAnnotations }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
        {{- end }}
        checksum/configmap-env: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }}
        {{- if .Values.customLdifFiles}}
        checksum/configmap-customldif: {{ include (print $.Template.BasePath "/configmap-customldif.yaml") . | sha256sum }}
        {{- end }}
      labels: {{- include "common.labels.standard" . | nindent 8 }}
        app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
        release: {{ .Release.Name }}
        {{- if .Values.podLabels }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
        {{- end }}
    spec:
      initContainers:
        - name: init-schema
          image: {{ include "openldap.initSchemaImage" . }}
          imagePullPolicy: {{ .Values.initSchema.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              cp -p -f /cm-schemas-acls/brep.ldif /custom_config/
              echo "let the replication takes care of everything :)"
            {{- if .Values.global.existingSecret }}
              sed -i -e "s/%%CONFIG_PASSWORD%%/${LDAP_CONFIG_ADMIN_PASSWORD}/g" /custom_config/*
              sed -i -e "s/%%ADMIN_PASSWORD%%/${LDAP_ADMIN_PASSWORD}/g" /custom_config/*
            {{- end }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
    {{- if .Values.customSchemaFiles }}
      {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: cm-custom-schema-files
              mountPath: /cm-schemas/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
      {{- end }}
            - name: custom-schema-files
              mountPath: /custom-schemas/
    {{- end }}
    {{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: cm-custom-ldif-files
              mountPath: /cm-ldifs/
            - name: custom-ldif-files
              mountPath: /custom-ldifs/
    {{- end }}
            - name: cm-replication-acls
              mountPath: /cm-schemas-acls
            - name: replication-acls
              mountPath: /custom_config
    {{- if .Values.global.existingSecret }}
          envFrom:
          - secretRef:
              name: {{ template "openldap.secretName" . }}
    {{- end }}
        {{- if .Values.initContainers }}
          {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
        {{- end }}
        - name: init-tls-secret
          image: {{ include "openldap.initTLSSecretImage" . }}
          imagePullPolicy: {{ .Values.initTLSSecret.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              {{- if and .Values.initTLSSecret.tls_enabled .Values.initTLSSecret.secret }}
              {{- else }}
              openssl req -x509 -newkey rsa:4096 -nodes -subj '/CN={{ .Values.global.ldapDomain }}' -keyout /tmp-certs/tls.key -out /tmp-certs/tls.crt -days 365
              chmod 777  /tmp-certs/*
              {{- end }}
              cp -Lr /tmp-certs/* /certs
              [ -e /certs/ca.crt ] || cp -a /certs/tls.crt /certs/ca.crt
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: certs
              mountPath: "/certs"
            - name: secret-certs
              mountPath: "/tmp-certs"
      {{- if .Values.volumePermissions.enabled }}
        - name: volume-permissions
          image: {{ include "openldap.volumePermissionsImage" . }}
          imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }}
          command: {{- include "common.tplvalues.render" (dict "value" .Values.volumePermissions.image.command "context" $) | nindent 12 }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.volumePermissions.resources }}
          resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - mountPath: /bitnami
              name: data
      {{- end }}
    
      serviceAccountName: {{ template "openldap.serviceAccountName" . }}
      {{- include "openldap.imagePullSecrets" . | nindent 6 }}
      {{- if .Values.hostAliases }}
      hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.affinity }}
      affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }}
      {{- else }}
      affinity:
        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "openldap-readonly" "context" $) | nindent 10 }}
        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "openldap-readonly" "context" $) | nindent 10 }}
        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
      {{- end }}
      {{- if .Values.nodeSelector }}
      nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.schedulerName }}
      schedulerName: {{- .Values.schedulerName | quote }}
      {{- end }}
      {{- if .Values.podSecurityContext.enabled }}
      securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
      {{- end }}
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName | quote }}
      {{- end }}
      {{- if .Values.tolerations }}
      tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: {{ include "openldap.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          env:
            - name: LDAP_EXTRA_SCHEMAS
              value: {{ print "cosine,inetorgperson,nis,brep,readonly" }}
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            {{- if .Values.extraEnvVars }}
            {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
            {{- end }}
          envFrom:
            {{- if .Values.extraEnvVarsCM }}
            - configMapRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
            {{- end }}
            - configMapRef:
                name: {{ template "openldap.fullname" . }}-env
            {{- if .Values.extraEnvVarsSecret }}
            - secretRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
            {{- end }}
            - secretRef:
                name: {{ template "openldap.secretName" . }}
          {{- if .Values.resources }}
          resources: {{- toYaml .Values.resources | nindent 12 }}
          {{- end }}
          ports:
            - name: ldap-port
              containerPort: 1389
            - name: ssl-ldap-port
              containerPort: 1636
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.readinessProbe.successThreshold }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.startupProbe.enabled }}
          startupProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.startupProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
            successThreshold: {{ .Values.startupProbe.successThreshold }}
            failureThreshold: {{ .Values.startupProbe.failureThreshold }}
          {{- else if .Values.customStartupProbe }}
          startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.lifecycleHooks }}
          lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: data
              mountPath: /bitnami/openldap/
            - name: certs
              mountPath: /opt/bitnami/openldap/certs
            - name: replication-acls
              mountPath: /opt/bitnami/openldap/etc/schema/brep.ldif
              subPath: brep.ldif
            - name: readonly-ldif
              mountPath: /opt/bitnami/openldap/etc/schema/readonly.ldif
              subPath: readonly.ldif
{{- if .Values.customSchemaFiles}}
            {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: custom-schema-files
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- end }}

{{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: custom-ldif-files
              mountPath: /ldifs/
{{- end }}
{{- range .Values.customFileSets }}
{{- $fs := . }}
{{- range .files }}
            - name: {{ $fs.name }}
              mountPath: {{ $fs.targetPath }}/{{ .filename }}
              subPath: {{ .filename }}
{{- end }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.sidecars }}
{{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }}
{{- end }}
      volumes:
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.existingClaim }}
        - name: data
          persistentVolumeClaim:
            claimName: {{ .Values.persistence.existingClaim }}
{{- end }}
{{- end }}
        - name: cm-replication-acls
          configMap:
            name: {{ template "openldap.fullname" . }}-replication-acls
        - name: replication-acls
          emptyDir:
            medium: Memory

{{- if .Values.customLdifFiles }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customldif
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- else if .Values.customLdifCm }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ .Values.customLdifCm }}
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- end }}
{{- if .Values.customSchemaFiles }}
        - name: cm-custom-schema-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customschema
        - name: custom-schema-files
          emptyDir:
            medium: Memory
{{- end }}
        - name: readonly-ldif
          configMap:
            name: {{ template "openldap.fullname" . }}-readonly
        - name: certs
          emptyDir:
            medium: Memory
{{- if .Values.initTLSSecret.tls_enabled }}
        - name: secret-certs
          secret:
            secretName: {{ .Values.initTLSSecret.secret }}
{{- else }}
        - name: secret-certs
          emptyDir:
            medium: Memory
{{- end }}
{{- range .Values.customFileSets }}
        - name: {{ .name }}
          configMap:
            name: {{ template "openldap.fullname" $ }}-fs-{{ .name }}
{{- end }}
        {{- if .Values.extraVolumes }}
        {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }}
        {{- end }}
{{- if and (not .Values.persistence.existingClaim)  .Values.persistence.enabled }}
  volumeClaimTemplates:
    - metadata:
        name: data
        annotations:
        {{- range $key, $value := .Values.persistence.annotations }}
          {{ $key }}: {{ $value }}
        {{- end }}
      spec:
        accessModes:
        {{- range .Values.persistence.accessModes }}
          - {{ . | quote }}
        {{- end }}
        resources:
          requests:
            storage: {{ .Values.persistence.size | quote }}
      {{- if .Values.persistence.storageClass }}
      {{- if (eq "-" .Values.persistence.storageClass) }}
        storageClassName: ""
      {{- else }}
        storageClassName: "{{ .Values.persistence.storageClass }}"
      {{- end }}
{{- end }}
{{- else if (not .Values.persistence.enabled) }}
        - name: data
          emptyDir: {}
{{- end }}
{{- end }}

@jp-gouin
Copy link
Owner

jp-gouin commented Jul 9, 2024

Hi @davidfrickert
Did you take a look at my previous comment ?

@davidfrickert
Copy link
Contributor Author

Hi @davidfrickert Did you take a look at my previous comment ?

hey, sorry for the delay, been sick and working on other stuff.

I think that your proposal doesn't achieve a fully read-only status as someone with the global admin credentials can still write through that connection which is not ideal.

The setup using the removal of the olcMultiProvider from the read only replica makes it truly read only such that writes are always rejected, so it seems better.

What do you think?

@jp-gouin
Copy link
Owner

Hey no worries

So how does the replication work if the base is in total read-only ?

@davidfrickert
Copy link
Contributor Author

Hey no worries

So how does the replication work if the base is in total read-only ?

It is in read-only from LDAP operations but syncrepl can still replicate fine (tested)
Don't know enough about the intricacies of OpenLDAP to explain it but I assume that LDAP operations and syncrepl operate in different levels?

@jp-gouin
Copy link
Owner

jp-gouin commented Jul 14, 2024

Alright to be honest I'm a bit lost in all the comments 😅

Can you summarize what is working and how to make it work ? Does the last commit reflect it ?

Are schemas replicated from master nodes ?

@pritchardtw
Copy link

I skipped a few comments, but I think what you guys are looking for is the concept of a 'consumer'. In openldap a consumer does not have a syncprov overlay and it should not accept writes.

I am also not sure read replicas fit the semantics of a stateful set. I'd be curious to find other industry examples of read replica manifests.

@jp-gouin
Copy link
Owner

jp-gouin commented Aug 9, 2024

Yes @pritchardtw this is what is suggested above , to only have read only nodes performing a base replication with syncrepl and no overlay.
But you still need a user to do the write (admin)
From what I understood @davidfrickert has a better solution I'm just waiting for his reply to my comment above.

To echo what you said , it shouldn't be a statefulset it should be a deployment for the readonly nodes (at least in my scenario)

@davidfrickert
Copy link
Contributor Author

I'm not that well versed in OpenLDAP so the current implementation might not be ideal.
In the current implementation, the read-only replicas still receive replication so they need to be a statefulset (each replica has its own persisted data).

If the consumer option is easier to manage and it provides the same protection (no write for any user, not even the admin user), then that sounds great.

@davidfrickert
Copy link
Contributor Author

The last commit does work but it seems a bit shaky, as it requires you to manually remove the olcMultiProvider config from both databases of the read-only replica to make it read-only. This could probably be achieved with a Job (?)

But as said above if you guys think there is a way to get this read-only functionality without needing to replicate data then i'm all for it!

@jp-gouin
Copy link
Owner

hi folks,

I missed this in the documentation for olcReadonly

5.2.5.3. olcReadonly { TRUE | FALSE }
This directive .... If set on a consumer, modifications sent by syncrepl will still occur.

By setting our readonly node as a consumer the syncrepl will still occur.

So by combining our two approach @davidfrickert we can actually have a bunch of readonly node replicating against the main ones without interfering in the replication.

Setting a node as consumer can be achieve using only brep as "replication" schema and then we can use the olcReadOnly: TRUE in the read-only configmap.

You don't need to do any post-install steps.

Does that sounds good to you ?

Note: I'll look into converting the readonly nodes to Deployment instead of Statefulset but I want to be sure that this approach is working for you first

Full solution below

_helper.tpl

{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "openldap.name" -}}
{{- default .Release.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "openldap.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Release.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "openldap.chart" -}}
{{- printf "%s-%s" .Release.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create the name of the service account to use
*/}}
{{- define "openldap.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
    {{ default (printf "%s-foo" (include "common.names.fullname" .)) .Values.serviceAccount.name }}
{{- else -}}
    {{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

{{/*
Generate chart secret name
*/}}
{{- define "openldap.secretName" -}}
{{ default (include "openldap.fullname" .) .Values.global.existingSecret }}
{{- end -}}

{{/*
Generate olcServerID list
*/}}
{{- define "olcServerIDs" }}
{{- $name := (include "openldap.fullname" .) }}
{{- $namespace := .Release.Namespace }}
{{- $cluster := .Values.replication.clusterName }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcServerID: {{ $index1 }} ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389
  {{- end -}}
{{- end -}}

{{/*
Generate olcSyncRepl list
*/}}
{{- define "olcSyncRepls" -}}
{{- $name := (include "openldap.fullname" .) }}
{{- $namespace := .Release.Namespace }}
{{- $bindDNUser := .Values.global.adminUser }}
{{- $cluster := .Values.replication.clusterName }}
{{- $configPassword :=  ternary .Values.global.configPassword "%%CONFIG_PASSWORD%%" (empty .Values.global.existingSecret) }}
{{- $retry := .Values.replication.retry }}
{{- $timeout := .Values.replication.timeout }}
{{- $starttls := .Values.replication.starttls }}
{{- $tls_reqcert := .Values.replication.tls_reqcert }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcSyncRepl: rid=00{{ $index1 }} provider=ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389 binddn="cn={{ $bindDNUser }},cn=config" bindmethod=simple credentials={{ $configPassword }} searchbase="cn=config" type=refreshAndPersist retry="{{ $retry }} +" timeout={{ $timeout }} starttls={{ $starttls }} tls_reqcert={{ $tls_reqcert }}
  {{- end -}}
{{- end -}}

{{/*
Generate olcSyncRepl list
*/}}
{{- define "olcSyncRepls2" -}}
{{- $name := (include "openldap.fullname" .) }}
{{- $domain := (include "global.baseDomain" .) }}
{{- $bindDNUser := .Values.global.adminUser }} 
{{- $namespace := .Release.Namespace }}
{{- $cluster := .Values.replication.clusterName }}
{{- $adminPassword := ternary .Values.global.adminPassword "%%ADMIN_PASSWORD%%" (empty .Values.global.existingSecret) }}
{{- $retry := .Values.replication.retry }}
{{- $timeout := .Values.replication.timeout }}
{{- $starttls := .Values.replication.starttls }}
{{- $tls_reqcert := .Values.replication.tls_reqcert }}
{{- $interval := .Values.replication.interval }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcSyncrepl:
      rid=10{{ $index1 }}
      provider=ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389
      binddn={{ printf "cn=%s,%s" $bindDNUser $domain }}
      bindmethod=simple
      credentials={{ $adminPassword }}
      searchbase={{ $domain }}
      type=refreshAndPersist
      interval={{ $interval }}
      network-timeout=0
      retry="{{ $retry }} +"
      timeout={{ $timeout }}
      starttls={{ $starttls }}
      tls_reqcert={{ $tls_reqcert }}
  {{- end -}}
{{- end -}}

{{/*
Renders a value that contains template.
Usage:
{{ include "openldap.tplValue" ( dict "value" .Values.path.to.the.Value "context" $) }}
*/}}
{{- define "openldap.tplValue" -}}
    {{- if typeIs "string" .value }}
        {{- tpl .value .context }}
    {{- else }}
        {{- tpl (.value | toYaml) .context }}
    {{- end }}
{{- end -}}

{{/*
Return the proper Openldap image name
*/}}
{{- define "openldap.image" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Docker Image Registry Secret Names
*/}}
{{- define "openldap.imagePullSecrets" -}}
{{ include "common.images.pullSecrets" (dict "images" (list .Values.image ) "global" .Values.global) }}
{{- end -}}


{{/*
Return the proper Openldap init container image name
*/}}
{{- define "openldap.initTLSSecretImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.initTLSSecret.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Openldap init container image name
*/}}
{{- define "openldap.initSchemaImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.initSchema.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Openldap volume permissions init container image name
*/}}
{{- define "openldap.volumePermissionsImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) -}}
{{- end -}}


{{/*
Return the list of builtin schema files to mount
Cannot return list => return string comma separated
*/}}
{{- define "openldap.builtinSchemaFiles" -}}
  {{- $schemas := "" -}}
  {{- $context := index . "context" -}}
  {{- $mode := index . "mode" -}}
  {{- if $context.Values.replication.enabled -}}
    {{- if $mode -}}
      {{- $schemas = "brep,readonly,acls" -}}
    {{- else -}}
      {{- $schemas = "syncprov,serverid,csyncprov,rep,bsyncprov,brep,acls" -}}
    {{- end -}}
  {{- else -}}
    {{- $schemas = "acls" -}}
  {{- end -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the list of custom schema files to use
Cannot return list => return string comma separated
*/}}
{{- define "openldap.customSchemaFiles" -}}
  {{- $context := index . "context" -}}
  {{- $schemas := "" -}}
  {{- $schemas := ((join "," ($context.Values.customSchemaFiles | keys | sortAlpha))  | replace ".ldif" "") -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the list of all schema files to use
Cannot return list => return string comma separated
*/}}
{{- define "openldap.schemaFiles" -}}
  {{- $context := index . "context" -}}
  {{- $mode := index . "mode" -}}
  {{- $schemas := (include "openldap.builtinSchemaFiles" .) -}}
  {{- $custom_schemas := (include "openldap.customSchemaFiles" .) -}}
  {{- if gt (len $custom_schemas) 0 -}}
    {{- $schemas = print $schemas "," $custom_schemas  -}}
  {{- end -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the proper base domain
*/}}
{{- define "global.baseDomain" -}}
{{- $bd := include "tmp.baseDomain" .}}
{{- printf "%s" $bd | trimSuffix "," -}}
{{- end }}

{{/*
tmp method to iterate through the ldapDomain
*/}}
{{- define "tmp.baseDomain" -}}
{{- if regexMatch ".*=.*" .Values.global.ldapDomain }}
{{- printf "%s" .Values.global.ldapDomain }}
{{- else }}
{{- $parts := split "." .Values.global.ldapDomain }}
  {{- range $index, $part := $parts }}
  {{- $index1 := $index | add 1 -}}
dc={{ $part }},
  {{- end}}
  {{- end -}}
{{- end -}}

{{/*
Return the server name
*/}}
{{- define "global.server" -}}
{{- printf "%s.%s" .Release.Name .Release.Namespace  -}}
{{- end -}}

{{/*
Return the admin bindDN
*/}}
{{- define "global.bindDN" -}}
{{- printf "cn=%s,%s" .Values.global.adminUser (include "global.baseDomain" .) -}}
{{- end -}}

{{/*
Return the ldaps port
*/}}
{{- define "global.ldapsPort" -}}
{{- printf "%d" .Values.global.sslLdapPort  -}}
{{- end -}}

{{/*
Return the ldap port
*/}}
{{- define "global.ldapPort" -}}
{{- printf "%d" .Values.global.ldapPort  -}}
{{- end -}}

configmap-readonly.yaml

{{- if (gt (.Values.readOnlyReplicaCount | int) 0) }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "openldap.fullname" . }}-readonly
  labels:
    app: {{ template "openldap.name" . }}
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
{{- if .Values.extraLabels }}
{{ toYaml .Values.extraLabels | indent 4 }}
{{- end }}
data:
  readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    replace: olcReadOnly
    olcReadOnly: TRUE
{{- end }}

statefulset-readonly.yaml

{{- if (gt (.Values.readOnlyReplicaCount | int) 0) }}
apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
  name:  {{ template "openldap.fullname" . }}-readonly
  labels: {{- include "common.labels.standard" . | nindent 4 }}
    app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  {{- if .Values.commonAnnotations }}
  annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
  {{- end }}
spec:
  replicas: {{ .Values.readOnlyReplicaCount }}
  selector:
    matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }}
      app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
  serviceName: {{ template "openldap.fullname" . }}-headless-readonly
  {{- if .Values.updateStrategy }}
  updateStrategy:
{{ toYaml .Values.updateStrategy | nindent 4 }}
  {{- end }}
  template:
    metadata:
      annotations:
        {{- if .Values.podAnnotations }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
        {{- end }}
        checksum/configmap-env: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }}
        {{- if .Values.customLdifFiles}}
        checksum/configmap-customldif: {{ include (print $.Template.BasePath "/configmap-customldif.yaml") . | sha256sum }}
        {{- end }}
      labels: {{- include "common.labels.standard" . | nindent 8 }}
        app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
        release: {{ .Release.Name }}
        {{- if .Values.podLabels }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
        {{- end }}
    spec:
      initContainers:
        - name: init-schema
          image: {{ include "openldap.initSchemaImage" . }}
          imagePullPolicy: {{ .Values.initSchema.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              cp -p -f /cm-schemas-acls/brep.ldif /custom_config/
              echo "let the replication takes care of everything :)"
            {{- if .Values.global.existingSecret }}
              sed -i -e "s/%%CONFIG_PASSWORD%%/${LDAP_CONFIG_ADMIN_PASSWORD}/g" /custom_config/*
              sed -i -e "s/%%ADMIN_PASSWORD%%/${LDAP_ADMIN_PASSWORD}/g" /custom_config/*
            {{- end }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
    {{- if .Values.customSchemaFiles }}
      {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: cm-custom-schema-files
              mountPath: /cm-schemas/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
      {{- end }}
            - name: custom-schema-files
              mountPath: /custom-schemas/
    {{- end }}
    {{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: cm-custom-ldif-files
              mountPath: /cm-ldifs/
            - name: custom-ldif-files
              mountPath: /custom-ldifs/
    {{- end }}
            - name: cm-replication-acls
              mountPath: /cm-schemas-acls
            - name: replication-acls
              mountPath: /custom_config
    {{- if .Values.global.existingSecret }}
          envFrom:
          - secretRef:
              name: {{ template "openldap.secretName" . }}
    {{- end }}
        {{- if .Values.initContainers }}
          {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
        {{- end }}
        - name: init-tls-secret
          image: {{ include "openldap.initTLSSecretImage" . }}
          imagePullPolicy: {{ .Values.initTLSSecret.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              {{- if and .Values.initTLSSecret.tls_enabled .Values.initTLSSecret.secret }}
              {{- else }}
              openssl req -x509 -newkey rsa:4096 -nodes -subj '/CN={{ .Values.global.ldapDomain }}' -keyout /tmp-certs/tls.key -out /tmp-certs/tls.crt -days 365
              chmod 777  /tmp-certs/*
              {{- end }}
              cp -Lr /tmp-certs/* /certs
              [ -e /certs/ca.crt ] || cp -a /certs/tls.crt /certs/ca.crt
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: certs
              mountPath: "/certs"
            - name: secret-certs
              mountPath: "/tmp-certs"
      {{- if .Values.volumePermissions.enabled }}
        - name: volume-permissions
          image: {{ include "openldap.volumePermissionsImage" . }}
          imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }}
          command: {{- include "common.tplvalues.render" (dict "value" .Values.volumePermissions.image.command "context" $) | nindent 12 }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.volumePermissions.resources }}
          resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - mountPath: /bitnami
              name: data
      {{- end }}
    
      serviceAccountName: {{ template "openldap.serviceAccountName" . }}
      {{- include "openldap.imagePullSecrets" . | nindent 6 }}
      {{- if .Values.hostAliases }}
      hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.affinity }}
      affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }}
      {{- else }}
      affinity:
        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "openldap-readonly" "context" $) | nindent 10 }}
        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "openldap-readonly" "context" $) | nindent 10 }}
        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
      {{- end }}
      {{- if .Values.nodeSelector }}
      nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.schedulerName }}
      schedulerName: {{- .Values.schedulerName | quote }}
      {{- end }}
      {{- if .Values.podSecurityContext.enabled }}
      securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
      {{- end }}
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName | quote }}
      {{- end }}
      {{- if .Values.tolerations }}
      tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: {{ include "openldap.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          env:
            - name: LDAP_EXTRA_SCHEMAS
              value: {{ print "cosine,inetorgperson,nis," (include "openldap.schemaFiles" (dict "context" . "mode" "readonly")) }}
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            {{- if .Values.extraEnvVars }}
            {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
            {{- end }}
          envFrom:
            {{- if .Values.extraEnvVarsCM }}
            - configMapRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
            {{- end }}
            - configMapRef:
                name: {{ template "openldap.fullname" . }}-env
            {{- if .Values.extraEnvVarsSecret }}
            - secretRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
            {{- end }}
            - secretRef:
                name: {{ template "openldap.secretName" . }}
          {{- if .Values.resources }}
          resources: {{- toYaml .Values.resources | nindent 12 }}
          {{- end }}
          ports:
            - name: ldap-port
              containerPort: 1389
            - name: ssl-ldap-port
              containerPort: 1636
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.readinessProbe.successThreshold }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.startupProbe.enabled }}
          startupProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.startupProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
            successThreshold: {{ .Values.startupProbe.successThreshold }}
            failureThreshold: {{ .Values.startupProbe.failureThreshold }}
          {{- else if .Values.customStartupProbe }}
          startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.lifecycleHooks }}
          lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: data
              mountPath: /bitnami/openldap/
            - name: certs
              mountPath: /opt/bitnami/openldap/certs
            - name: replication-acls
              mountPath: /opt/bitnami/openldap/etc/schema/brep.ldif
              subPath: brep.ldif
            - name: readonly-ldif
              mountPath: /opt/bitnami/openldap/etc/schema/readonly.ldif
              subPath: readonly.ldif
{{- if .Values.customSchemaFiles}}
            {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: custom-schema-files
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- end }}

{{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: custom-ldif-files
              mountPath: /ldifs/
{{- end }}
{{- range .Values.customFileSets }}
{{- $fs := . }}
{{- range .files }}
            - name: {{ $fs.name }}
              mountPath: {{ $fs.targetPath }}/{{ .filename }}
              subPath: {{ .filename }}
{{- end }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.sidecars }}
{{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }}
{{- end }}
      volumes:
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.existingClaim }}
        - name: data
          persistentVolumeClaim:
            claimName: {{ .Values.persistence.existingClaim }}
{{- end }}
{{- end }}
        - name: cm-replication-acls
          configMap:
            name: {{ template "openldap.fullname" . }}-replication-acls
        - name: replication-acls
          emptyDir:
            medium: Memory

{{- if .Values.customLdifFiles }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customldif
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- else if .Values.customLdifCm }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ .Values.customLdifCm }}
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- end }}
{{- if .Values.customSchemaFiles }}
        - name: cm-custom-schema-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customschema
        - name: custom-schema-files
          emptyDir:
            medium: Memory
{{- end }}
        - name: readonly-ldif
          configMap:
            name: {{ template "openldap.fullname" . }}-readonly
        - name: certs
          emptyDir:
            medium: Memory
{{- if .Values.initTLSSecret.tls_enabled }}
        - name: secret-certs
          secret:
            secretName: {{ .Values.initTLSSecret.secret }}
{{- else }}
        - name: secret-certs
          emptyDir:
            medium: Memory
{{- end }}
{{- range .Values.customFileSets }}
        - name: {{ .name }}
          configMap:
            name: {{ template "openldap.fullname" $ }}-fs-{{ .name }}
{{- end }}
        {{- if .Values.extraVolumes }}
        {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }}
        {{- end }}
{{- if and (not .Values.persistence.existingClaim)  .Values.persistence.enabled }}
  volumeClaimTemplates:
    - metadata:
        name: data
        annotations:
        {{- range $key, $value := .Values.persistence.annotations }}
          {{ $key }}: {{ $value }}
        {{- end }}
      spec:
        accessModes:
        {{- range .Values.persistence.accessModes }}
          - {{ . | quote }}
        {{- end }}
        resources:
          requests:
            storage: {{ .Values.persistence.size | quote }}
      {{- if .Values.persistence.storageClass }}
      {{- if (eq "-" .Values.persistence.storageClass) }}
        storageClassName: ""
      {{- else }}
        storageClassName: "{{ .Values.persistence.storageClass }}"
      {{- end }}
{{- end }}
{{- else if (not .Values.persistence.enabled) }}
        - name: data
          emptyDir: {}
{{- end }}
{{- end }}

statefulset.yaml

apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
  name:  {{ template "openldap.fullname" . }}
  labels: {{- include "common.labels.standard" . | nindent 4 }}
    app.kubernetes.io/component: {{ template "openldap.fullname" . }}
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  {{- if .Values.commonAnnotations }}
  annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
  {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }}
      app.kubernetes.io/component: {{ template "openldap.fullname" . }}
  serviceName: {{ template "openldap.fullname" . }}-headless
  {{- if .Values.updateStrategy }}
  updateStrategy:
{{ toYaml .Values.updateStrategy | nindent 4 }}
  {{- end }}
  template:
    metadata:
      annotations:
        {{- if .Values.podAnnotations }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
        {{- end }}
        checksum/configmap-env: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }}
        {{- if .Values.customLdifFiles}}
        checksum/configmap-customldif: {{ include (print $.Template.BasePath "/configmap-customldif.yaml") . | sha256sum }}
        {{- end }}
      labels: {{- include "common.labels.standard" . | nindent 8 }}
        app.kubernetes.io/component: {{ template "openldap.fullname" . }}
        release: {{ .Release.Name }}
        {{- if .Values.podLabels }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
        {{- end }}
    spec:
      initContainers:
        - name: init-schema
          image: {{ include "openldap.initSchemaImage" . }}
          imagePullPolicy: {{ .Values.initSchema.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              host=$(hostname)
              if [ "$host" = "{{ template "openldap.fullname" . }}-0" ]
              then
                echo "This is the main openldap so let's init all additional schemas and ldifs here"
                cp -p -f /cm-schemas-acls/*.ldif /custom_config/ 
                if [ -d /cm-schemas ]; then
                  cp -p -f /cm-schemas/*.ldif /custom-schemas/ 
                fi
                if [ -d /cm-ldifs ]; then
                  cp -p -f /cm-ldifs/*.ldif /custom-ldifs/ 
                fi
              else
                cp -p -f /cm-schemas-acls/*.ldif /custom_config/
                echo "let the replication takes care of everything :)"
              fi
            {{- if .Values.global.existingSecret }}
              sed -i -e "s/%%CONFIG_PASSWORD%%/${LDAP_CONFIG_ADMIN_PASSWORD}/g" /custom_config/*
              sed -i -e "s/%%ADMIN_PASSWORD%%/${LDAP_ADMIN_PASSWORD}/g" /custom_config/*
            {{- end }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
    {{- if .Values.customSchemaFiles }}
      {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: cm-custom-schema-files
              mountPath: /cm-schemas/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
      {{- end }}
            - name: custom-schema-files
              mountPath: /custom-schemas/
    {{- end }}
    {{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: cm-custom-ldif-files
              mountPath: /cm-ldifs/
            - name: custom-ldif-files
              mountPath: /custom-ldifs/
    {{- end }}
            - name: cm-replication-acls
              mountPath: "/cm-schemas-acls"
            - name: replication-acls
              mountPath: "/custom_config"
    {{- if .Values.global.existingSecret }}
          envFrom:
          - secretRef:
              name: {{ template "openldap.secretName" . }}
    {{- end }}
        {{- if .Values.initContainers }}
          {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
        {{- end }}
        - name: init-tls-secret
          image: {{ include "openldap.initTLSSecretImage" . }}
          imagePullPolicy: {{ .Values.initTLSSecret.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              {{- if and .Values.initTLSSecret.tls_enabled .Values.initTLSSecret.secret }}
              {{- else }}
              openssl req -x509 -newkey rsa:4096 -nodes -subj '/CN={{ .Values.global.ldapDomain }}' -keyout /tmp-certs/tls.key -out /tmp-certs/tls.crt -days 365
              chmod 777  /tmp-certs/*
              {{- end }}
              cp -Lr /tmp-certs/* /certs
              [ -e /certs/ca.crt ] || cp -a /certs/tls.crt /certs/ca.crt
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: certs
              mountPath: "/certs"
            - name: secret-certs
              mountPath: "/tmp-certs"
      {{- if .Values.volumePermissions.enabled }}
        - name: volume-permissions
          image: {{ include "openldap.volumePermissionsImage" . }}
          imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }}
          command: {{- include "common.tplvalues.render" (dict "value" .Values.volumePermissions.image.command "context" $) | nindent 12 }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.volumePermissions.resources }}
          resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - mountPath: /bitnami
              name: data
      {{- end }}
    
      serviceAccountName: {{ template "openldap.serviceAccountName" . }}
      {{- include "openldap.imagePullSecrets" . | nindent 6 }}
      {{- if .Values.hostAliases }}
      hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.affinity }}
      affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }}
      {{- else }}
      affinity:
        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "openldap" "context" $) | nindent 10 }}
        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "openldap" "context" $) | nindent 10 }}
        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
      {{- end }}
      {{- if .Values.nodeSelector }}
      nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.schedulerName }}
      schedulerName: {{- .Values.schedulerName | quote }}
      {{- end }}
      {{- if .Values.podSecurityContext.enabled }}
      securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
      {{- end }}
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName | quote }}
      {{- end }}
      {{- if .Values.tolerations }}
      tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: {{ include "openldap.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          env:
            - name: LDAP_EXTRA_SCHEMAS
              value: {{ print "cosine,inetorgperson,nis," (include "openldap.schemaFiles" (dict "context" . )) }}
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            {{- if .Values.extraEnvVars }}
            {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
            {{- end }}
          envFrom:
            {{- if .Values.extraEnvVarsCM }}
            - configMapRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
            {{- end }}
            - configMapRef:
                name: {{ template "openldap.fullname" . }}-env
            {{- if .Values.extraEnvVarsSecret }}
            - secretRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
            {{- end }}
            - secretRef:
                name: {{ template "openldap.secretName" . }}
          {{- if .Values.resources }}
          resources: {{- toYaml .Values.resources | nindent 12 }}
          {{- end }}
          ports:
            - name: ldap-port
              containerPort: 1389
            - name: ssl-ldap-port
              containerPort: 1636
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.readinessProbe.successThreshold }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.startupProbe.enabled }}
          startupProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.startupProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
            successThreshold: {{ .Values.startupProbe.successThreshold }}
            failureThreshold: {{ .Values.startupProbe.failureThreshold }}
          {{- else if .Values.customStartupProbe }}
          startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.lifecycleHooks }}
          lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: data
              mountPath: /bitnami/openldap/
            - name: certs
              mountPath: /opt/bitnami/openldap/certs
            {{- range $file := (include "openldap.builtinSchemaFiles" . | split ",") }}
            - name: replication-acls
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- if .Values.customSchemaFiles}}
            {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: custom-schema-files
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- end }}
{{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: custom-ldif-files
              mountPath: /ldifs/
{{- end }}
{{- range .Values.customFileSets }}
{{- $fs := . }}
{{- range .files }}
            - name: {{ $fs.name }}
              mountPath: {{ $fs.targetPath }}/{{ .filename }}
              subPath: {{ .filename }}
{{- end }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.sidecars }}
{{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }}
{{- end }}
      volumes:
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.existingClaim }}
        - name: data
          persistentVolumeClaim:
            claimName: {{ .Values.persistence.existingClaim }}
{{- end }}
{{- end }}
        - name: cm-replication-acls
          configMap:
            name: {{ template "openldap.fullname" . }}-replication-acls
        - name: replication-acls
          emptyDir:
            medium: Memory

{{- if .Values.customLdifFiles }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customldif
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- else if .Values.customLdifCm }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ .Values.customLdifCm }}
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- end }}
{{- if .Values.customSchemaFiles }}
        - name: cm-custom-schema-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customschema
        - name: custom-schema-files
          emptyDir:
            medium: Memory
{{- end }}
        - name: certs
          emptyDir:
            medium: Memory
{{- if .Values.initTLSSecret.tls_enabled }}
        - name: secret-certs
          secret:
            secretName: {{ .Values.initTLSSecret.secret }}
{{- else }}
        - name: secret-certs
          emptyDir:
            medium: Memory
{{- end }}
{{- range .Values.customFileSets }}
        - name: {{ .name }}
          configMap:
            name: {{ template "openldap.fullname" $ }}-fs-{{ .name }}
{{- end }}
        {{- if .Values.extraVolumes }}
        {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }}
        {{- end }}
{{- if and (not .Values.persistence.existingClaim)  .Values.persistence.enabled }}
  volumeClaimTemplates:
    - metadata:
        name: data
        annotations:
        {{- range $key, $value := .Values.persistence.annotations }}
          {{ $key }}: {{ $value }}
        {{- end }}
      spec:
        accessModes:
        {{- range .Values.persistence.accessModes }}
          - {{ . | quote }}
        {{- end }}
        resources:
          requests:
            storage: {{ .Values.persistence.size | quote }}
      {{- if .Values.persistence.storageClass }}
      {{- if (eq "-" .Values.persistence.storageClass) }}
        storageClassName: ""
      {{- else }}
        storageClassName: "{{ .Values.persistence.storageClass }}"
      {{- end }}
{{- end }}
{{- else if (not .Values.persistence.enabled) }}
        - name: data
          emptyDir: {}
{{- end }}

@davidfrickert
Copy link
Contributor Author

hi folks,

I missed this in the documentation for olcReadonly

5.2.5.3. olcReadonly { TRUE | FALSE }
This directive .... If set on a consumer, modifications sent by syncrepl will still occur.

By setting our readonly node as a consumer the syncrepl will still occur.

So by combining our two approach @davidfrickert we can actually have a bunch of readonly node replicating against the main ones without interfering in the replication.

Setting a node as consumer can be achieve using only brep as "replication" schema and then we can use the olcReadOnly: TRUE in the read-only configmap.

You don't need to do any post-install steps.

Does that sounds good to you ?

Note: I'll look into converting the readonly nodes to Deployment instead of Statefulset but I want to be sure that this approach is working for you first

Full solution below

_helper.tpl

{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "openldap.name" -}}
{{- default .Release.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "openldap.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Release.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "openldap.chart" -}}
{{- printf "%s-%s" .Release.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create the name of the service account to use
*/}}
{{- define "openldap.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
    {{ default (printf "%s-foo" (include "common.names.fullname" .)) .Values.serviceAccount.name }}
{{- else -}}
    {{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

{{/*
Generate chart secret name
*/}}
{{- define "openldap.secretName" -}}
{{ default (include "openldap.fullname" .) .Values.global.existingSecret }}
{{- end -}}

{{/*
Generate olcServerID list
*/}}
{{- define "olcServerIDs" }}
{{- $name := (include "openldap.fullname" .) }}
{{- $namespace := .Release.Namespace }}
{{- $cluster := .Values.replication.clusterName }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcServerID: {{ $index1 }} ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389
  {{- end -}}
{{- end -}}

{{/*
Generate olcSyncRepl list
*/}}
{{- define "olcSyncRepls" -}}
{{- $name := (include "openldap.fullname" .) }}
{{- $namespace := .Release.Namespace }}
{{- $bindDNUser := .Values.global.adminUser }}
{{- $cluster := .Values.replication.clusterName }}
{{- $configPassword :=  ternary .Values.global.configPassword "%%CONFIG_PASSWORD%%" (empty .Values.global.existingSecret) }}
{{- $retry := .Values.replication.retry }}
{{- $timeout := .Values.replication.timeout }}
{{- $starttls := .Values.replication.starttls }}
{{- $tls_reqcert := .Values.replication.tls_reqcert }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcSyncRepl: rid=00{{ $index1 }} provider=ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389 binddn="cn={{ $bindDNUser }},cn=config" bindmethod=simple credentials={{ $configPassword }} searchbase="cn=config" type=refreshAndPersist retry="{{ $retry }} +" timeout={{ $timeout }} starttls={{ $starttls }} tls_reqcert={{ $tls_reqcert }}
  {{- end -}}
{{- end -}}

{{/*
Generate olcSyncRepl list
*/}}
{{- define "olcSyncRepls2" -}}
{{- $name := (include "openldap.fullname" .) }}
{{- $domain := (include "global.baseDomain" .) }}
{{- $bindDNUser := .Values.global.adminUser }} 
{{- $namespace := .Release.Namespace }}
{{- $cluster := .Values.replication.clusterName }}
{{- $adminPassword := ternary .Values.global.adminPassword "%%ADMIN_PASSWORD%%" (empty .Values.global.existingSecret) }}
{{- $retry := .Values.replication.retry }}
{{- $timeout := .Values.replication.timeout }}
{{- $starttls := .Values.replication.starttls }}
{{- $tls_reqcert := .Values.replication.tls_reqcert }}
{{- $interval := .Values.replication.interval }}
{{- $nodeCount := .Values.replicaCount | int }}
  {{- range $index0 := until $nodeCount }}
    {{- $index1 := $index0 | add1 }}
    olcSyncrepl:
      rid=10{{ $index1 }}
      provider=ldap://{{ $name }}-{{ $index0 }}.{{ $name }}-headless.{{ $namespace }}.svc.{{ $cluster }}:1389
      binddn={{ printf "cn=%s,%s" $bindDNUser $domain }}
      bindmethod=simple
      credentials={{ $adminPassword }}
      searchbase={{ $domain }}
      type=refreshAndPersist
      interval={{ $interval }}
      network-timeout=0
      retry="{{ $retry }} +"
      timeout={{ $timeout }}
      starttls={{ $starttls }}
      tls_reqcert={{ $tls_reqcert }}
  {{- end -}}
{{- end -}}

{{/*
Renders a value that contains template.
Usage:
{{ include "openldap.tplValue" ( dict "value" .Values.path.to.the.Value "context" $) }}
*/}}
{{- define "openldap.tplValue" -}}
    {{- if typeIs "string" .value }}
        {{- tpl .value .context }}
    {{- else }}
        {{- tpl (.value | toYaml) .context }}
    {{- end }}
{{- end -}}

{{/*
Return the proper Openldap image name
*/}}
{{- define "openldap.image" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Docker Image Registry Secret Names
*/}}
{{- define "openldap.imagePullSecrets" -}}
{{ include "common.images.pullSecrets" (dict "images" (list .Values.image ) "global" .Values.global) }}
{{- end -}}


{{/*
Return the proper Openldap init container image name
*/}}
{{- define "openldap.initTLSSecretImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.initTLSSecret.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Openldap init container image name
*/}}
{{- define "openldap.initSchemaImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.initSchema.image "global" .Values.global) -}}
{{- end -}}

{{/*
Return the proper Openldap volume permissions init container image name
*/}}
{{- define "openldap.volumePermissionsImage" -}}
{{- include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) -}}
{{- end -}}


{{/*
Return the list of builtin schema files to mount
Cannot return list => return string comma separated
*/}}
{{- define "openldap.builtinSchemaFiles" -}}
  {{- $schemas := "" -}}
  {{- $context := index . "context" -}}
  {{- $mode := index . "mode" -}}
  {{- if $context.Values.replication.enabled -}}
    {{- if $mode -}}
      {{- $schemas = "brep,readonly,acls" -}}
    {{- else -}}
      {{- $schemas = "syncprov,serverid,csyncprov,rep,bsyncprov,brep,acls" -}}
    {{- end -}}
  {{- else -}}
    {{- $schemas = "acls" -}}
  {{- end -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the list of custom schema files to use
Cannot return list => return string comma separated
*/}}
{{- define "openldap.customSchemaFiles" -}}
  {{- $context := index . "context" -}}
  {{- $schemas := "" -}}
  {{- $schemas := ((join "," ($context.Values.customSchemaFiles | keys | sortAlpha))  | replace ".ldif" "") -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the list of all schema files to use
Cannot return list => return string comma separated
*/}}
{{- define "openldap.schemaFiles" -}}
  {{- $context := index . "context" -}}
  {{- $mode := index . "mode" -}}
  {{- $schemas := (include "openldap.builtinSchemaFiles" .) -}}
  {{- $custom_schemas := (include "openldap.customSchemaFiles" .) -}}
  {{- if gt (len $custom_schemas) 0 -}}
    {{- $schemas = print $schemas "," $custom_schemas  -}}
  {{- end -}}
  {{- print $schemas -}}
{{- end -}}

{{/*
Return the proper base domain
*/}}
{{- define "global.baseDomain" -}}
{{- $bd := include "tmp.baseDomain" .}}
{{- printf "%s" $bd | trimSuffix "," -}}
{{- end }}

{{/*
tmp method to iterate through the ldapDomain
*/}}
{{- define "tmp.baseDomain" -}}
{{- if regexMatch ".*=.*" .Values.global.ldapDomain }}
{{- printf "%s" .Values.global.ldapDomain }}
{{- else }}
{{- $parts := split "." .Values.global.ldapDomain }}
  {{- range $index, $part := $parts }}
  {{- $index1 := $index | add 1 -}}
dc={{ $part }},
  {{- end}}
  {{- end -}}
{{- end -}}

{{/*
Return the server name
*/}}
{{- define "global.server" -}}
{{- printf "%s.%s" .Release.Name .Release.Namespace  -}}
{{- end -}}

{{/*
Return the admin bindDN
*/}}
{{- define "global.bindDN" -}}
{{- printf "cn=%s,%s" .Values.global.adminUser (include "global.baseDomain" .) -}}
{{- end -}}

{{/*
Return the ldaps port
*/}}
{{- define "global.ldapsPort" -}}
{{- printf "%d" .Values.global.sslLdapPort  -}}
{{- end -}}

{{/*
Return the ldap port
*/}}
{{- define "global.ldapPort" -}}
{{- printf "%d" .Values.global.ldapPort  -}}
{{- end -}}

configmap-readonly.yaml

{{- if (gt (.Values.readOnlyReplicaCount | int) 0) }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "openldap.fullname" . }}-readonly
  labels:
    app: {{ template "openldap.name" . }}
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
{{- if .Values.extraLabels }}
{{ toYaml .Values.extraLabels | indent 4 }}
{{- end }}
data:
  readonly.ldif: |
    dn: olcDatabase={2}mdb,cn=config
    changetype: modify
    replace: olcReadOnly
    olcReadOnly: TRUE
{{- end }}

statefulset-readonly.yaml

{{- if (gt (.Values.readOnlyReplicaCount | int) 0) }}
apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
  name:  {{ template "openldap.fullname" . }}-readonly
  labels: {{- include "common.labels.standard" . | nindent 4 }}
    app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  {{- if .Values.commonAnnotations }}
  annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
  {{- end }}
spec:
  replicas: {{ .Values.readOnlyReplicaCount }}
  selector:
    matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }}
      app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
  serviceName: {{ template "openldap.fullname" . }}-headless-readonly
  {{- if .Values.updateStrategy }}
  updateStrategy:
{{ toYaml .Values.updateStrategy | nindent 4 }}
  {{- end }}
  template:
    metadata:
      annotations:
        {{- if .Values.podAnnotations }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
        {{- end }}
        checksum/configmap-env: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }}
        {{- if .Values.customLdifFiles}}
        checksum/configmap-customldif: {{ include (print $.Template.BasePath "/configmap-customldif.yaml") . | sha256sum }}
        {{- end }}
      labels: {{- include "common.labels.standard" . | nindent 8 }}
        app.kubernetes.io/component: {{ template "openldap.fullname" . }}-readonly
        release: {{ .Release.Name }}
        {{- if .Values.podLabels }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
        {{- end }}
    spec:
      initContainers:
        - name: init-schema
          image: {{ include "openldap.initSchemaImage" . }}
          imagePullPolicy: {{ .Values.initSchema.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              cp -p -f /cm-schemas-acls/brep.ldif /custom_config/
              echo "let the replication takes care of everything :)"
            {{- if .Values.global.existingSecret }}
              sed -i -e "s/%%CONFIG_PASSWORD%%/${LDAP_CONFIG_ADMIN_PASSWORD}/g" /custom_config/*
              sed -i -e "s/%%ADMIN_PASSWORD%%/${LDAP_ADMIN_PASSWORD}/g" /custom_config/*
            {{- end }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
    {{- if .Values.customSchemaFiles }}
      {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: cm-custom-schema-files
              mountPath: /cm-schemas/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
      {{- end }}
            - name: custom-schema-files
              mountPath: /custom-schemas/
    {{- end }}
    {{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: cm-custom-ldif-files
              mountPath: /cm-ldifs/
            - name: custom-ldif-files
              mountPath: /custom-ldifs/
    {{- end }}
            - name: cm-replication-acls
              mountPath: /cm-schemas-acls
            - name: replication-acls
              mountPath: /custom_config
    {{- if .Values.global.existingSecret }}
          envFrom:
          - secretRef:
              name: {{ template "openldap.secretName" . }}
    {{- end }}
        {{- if .Values.initContainers }}
          {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
        {{- end }}
        - name: init-tls-secret
          image: {{ include "openldap.initTLSSecretImage" . }}
          imagePullPolicy: {{ .Values.initTLSSecret.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              {{- if and .Values.initTLSSecret.tls_enabled .Values.initTLSSecret.secret }}
              {{- else }}
              openssl req -x509 -newkey rsa:4096 -nodes -subj '/CN={{ .Values.global.ldapDomain }}' -keyout /tmp-certs/tls.key -out /tmp-certs/tls.crt -days 365
              chmod 777  /tmp-certs/*
              {{- end }}
              cp -Lr /tmp-certs/* /certs
              [ -e /certs/ca.crt ] || cp -a /certs/tls.crt /certs/ca.crt
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: certs
              mountPath: "/certs"
            - name: secret-certs
              mountPath: "/tmp-certs"
      {{- if .Values.volumePermissions.enabled }}
        - name: volume-permissions
          image: {{ include "openldap.volumePermissionsImage" . }}
          imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }}
          command: {{- include "common.tplvalues.render" (dict "value" .Values.volumePermissions.image.command "context" $) | nindent 12 }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.volumePermissions.resources }}
          resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - mountPath: /bitnami
              name: data
      {{- end }}
    
      serviceAccountName: {{ template "openldap.serviceAccountName" . }}
      {{- include "openldap.imagePullSecrets" . | nindent 6 }}
      {{- if .Values.hostAliases }}
      hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.affinity }}
      affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }}
      {{- else }}
      affinity:
        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "openldap-readonly" "context" $) | nindent 10 }}
        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "openldap-readonly" "context" $) | nindent 10 }}
        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
      {{- end }}
      {{- if .Values.nodeSelector }}
      nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.schedulerName }}
      schedulerName: {{- .Values.schedulerName | quote }}
      {{- end }}
      {{- if .Values.podSecurityContext.enabled }}
      securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
      {{- end }}
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName | quote }}
      {{- end }}
      {{- if .Values.tolerations }}
      tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: {{ include "openldap.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          env:
            - name: LDAP_EXTRA_SCHEMAS
              value: {{ print "cosine,inetorgperson,nis," (include "openldap.schemaFiles" (dict "context" . "mode" "readonly")) }}
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            {{- if .Values.extraEnvVars }}
            {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
            {{- end }}
          envFrom:
            {{- if .Values.extraEnvVarsCM }}
            - configMapRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
            {{- end }}
            - configMapRef:
                name: {{ template "openldap.fullname" . }}-env
            {{- if .Values.extraEnvVarsSecret }}
            - secretRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
            {{- end }}
            - secretRef:
                name: {{ template "openldap.secretName" . }}
          {{- if .Values.resources }}
          resources: {{- toYaml .Values.resources | nindent 12 }}
          {{- end }}
          ports:
            - name: ldap-port
              containerPort: 1389
            - name: ssl-ldap-port
              containerPort: 1636
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.readinessProbe.successThreshold }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.startupProbe.enabled }}
          startupProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.startupProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
            successThreshold: {{ .Values.startupProbe.successThreshold }}
            failureThreshold: {{ .Values.startupProbe.failureThreshold }}
          {{- else if .Values.customStartupProbe }}
          startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.lifecycleHooks }}
          lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: data
              mountPath: /bitnami/openldap/
            - name: certs
              mountPath: /opt/bitnami/openldap/certs
            - name: replication-acls
              mountPath: /opt/bitnami/openldap/etc/schema/brep.ldif
              subPath: brep.ldif
            - name: readonly-ldif
              mountPath: /opt/bitnami/openldap/etc/schema/readonly.ldif
              subPath: readonly.ldif
{{- if .Values.customSchemaFiles}}
            {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: custom-schema-files
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- end }}

{{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: custom-ldif-files
              mountPath: /ldifs/
{{- end }}
{{- range .Values.customFileSets }}
{{- $fs := . }}
{{- range .files }}
            - name: {{ $fs.name }}
              mountPath: {{ $fs.targetPath }}/{{ .filename }}
              subPath: {{ .filename }}
{{- end }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.sidecars }}
{{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }}
{{- end }}
      volumes:
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.existingClaim }}
        - name: data
          persistentVolumeClaim:
            claimName: {{ .Values.persistence.existingClaim }}
{{- end }}
{{- end }}
        - name: cm-replication-acls
          configMap:
            name: {{ template "openldap.fullname" . }}-replication-acls
        - name: replication-acls
          emptyDir:
            medium: Memory

{{- if .Values.customLdifFiles }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customldif
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- else if .Values.customLdifCm }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ .Values.customLdifCm }}
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- end }}
{{- if .Values.customSchemaFiles }}
        - name: cm-custom-schema-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customschema
        - name: custom-schema-files
          emptyDir:
            medium: Memory
{{- end }}
        - name: readonly-ldif
          configMap:
            name: {{ template "openldap.fullname" . }}-readonly
        - name: certs
          emptyDir:
            medium: Memory
{{- if .Values.initTLSSecret.tls_enabled }}
        - name: secret-certs
          secret:
            secretName: {{ .Values.initTLSSecret.secret }}
{{- else }}
        - name: secret-certs
          emptyDir:
            medium: Memory
{{- end }}
{{- range .Values.customFileSets }}
        - name: {{ .name }}
          configMap:
            name: {{ template "openldap.fullname" $ }}-fs-{{ .name }}
{{- end }}
        {{- if .Values.extraVolumes }}
        {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }}
        {{- end }}
{{- if and (not .Values.persistence.existingClaim)  .Values.persistence.enabled }}
  volumeClaimTemplates:
    - metadata:
        name: data
        annotations:
        {{- range $key, $value := .Values.persistence.annotations }}
          {{ $key }}: {{ $value }}
        {{- end }}
      spec:
        accessModes:
        {{- range .Values.persistence.accessModes }}
          - {{ . | quote }}
        {{- end }}
        resources:
          requests:
            storage: {{ .Values.persistence.size | quote }}
      {{- if .Values.persistence.storageClass }}
      {{- if (eq "-" .Values.persistence.storageClass) }}
        storageClassName: ""
      {{- else }}
        storageClassName: "{{ .Values.persistence.storageClass }}"
      {{- end }}
{{- end }}
{{- else if (not .Values.persistence.enabled) }}
        - name: data
          emptyDir: {}
{{- end }}
{{- end }}

statefulset.yaml

apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
  name:  {{ template "openldap.fullname" . }}
  labels: {{- include "common.labels.standard" . | nindent 4 }}
    app.kubernetes.io/component: {{ template "openldap.fullname" . }}
    chart: {{ template "openldap.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  {{- if .Values.commonAnnotations }}
  annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
  {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }}
      app.kubernetes.io/component: {{ template "openldap.fullname" . }}
  serviceName: {{ template "openldap.fullname" . }}-headless
  {{- if .Values.updateStrategy }}
  updateStrategy:
{{ toYaml .Values.updateStrategy | nindent 4 }}
  {{- end }}
  template:
    metadata:
      annotations:
        {{- if .Values.podAnnotations }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
        {{- end }}
        checksum/configmap-env: {{ include (print $.Template.BasePath "/configmap-env.yaml") . | sha256sum }}
        {{- if .Values.customLdifFiles}}
        checksum/configmap-customldif: {{ include (print $.Template.BasePath "/configmap-customldif.yaml") . | sha256sum }}
        {{- end }}
      labels: {{- include "common.labels.standard" . | nindent 8 }}
        app.kubernetes.io/component: {{ template "openldap.fullname" . }}
        release: {{ .Release.Name }}
        {{- if .Values.podLabels }}
        {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }}
        {{- end }}
    spec:
      initContainers:
        - name: init-schema
          image: {{ include "openldap.initSchemaImage" . }}
          imagePullPolicy: {{ .Values.initSchema.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              host=$(hostname)
              if [ "$host" = "{{ template "openldap.fullname" . }}-0" ]
              then
                echo "This is the main openldap so let's init all additional schemas and ldifs here"
                cp -p -f /cm-schemas-acls/*.ldif /custom_config/ 
                if [ -d /cm-schemas ]; then
                  cp -p -f /cm-schemas/*.ldif /custom-schemas/ 
                fi
                if [ -d /cm-ldifs ]; then
                  cp -p -f /cm-ldifs/*.ldif /custom-ldifs/ 
                fi
              else
                cp -p -f /cm-schemas-acls/*.ldif /custom_config/
                echo "let the replication takes care of everything :)"
              fi
            {{- if .Values.global.existingSecret }}
              sed -i -e "s/%%CONFIG_PASSWORD%%/${LDAP_CONFIG_ADMIN_PASSWORD}/g" /custom_config/*
              sed -i -e "s/%%ADMIN_PASSWORD%%/${LDAP_ADMIN_PASSWORD}/g" /custom_config/*
            {{- end }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
    {{- if .Values.customSchemaFiles }}
      {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: cm-custom-schema-files
              mountPath: /cm-schemas/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
      {{- end }}
            - name: custom-schema-files
              mountPath: /custom-schemas/
    {{- end }}
    {{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: cm-custom-ldif-files
              mountPath: /cm-ldifs/
            - name: custom-ldif-files
              mountPath: /custom-ldifs/
    {{- end }}
            - name: cm-replication-acls
              mountPath: "/cm-schemas-acls"
            - name: replication-acls
              mountPath: "/custom_config"
    {{- if .Values.global.existingSecret }}
          envFrom:
          - secretRef:
              name: {{ template "openldap.secretName" . }}
    {{- end }}
        {{- if .Values.initContainers }}
          {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
        {{- end }}
        - name: init-tls-secret
          image: {{ include "openldap.initTLSSecretImage" . }}
          imagePullPolicy: {{ .Values.initTLSSecret.image.pullPolicy | quote }}
          command:
            - sh
            - -c
            - |
              {{- if and .Values.initTLSSecret.tls_enabled .Values.initTLSSecret.secret }}
              {{- else }}
              openssl req -x509 -newkey rsa:4096 -nodes -subj '/CN={{ .Values.global.ldapDomain }}' -keyout /tmp-certs/tls.key -out /tmp-certs/tls.crt -days 365
              chmod 777  /tmp-certs/*
              {{- end }}
              cp -Lr /tmp-certs/* /certs
              [ -e /certs/ca.crt ] || cp -a /certs/tls.crt /certs/ca.crt
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.initTLSSecret.resources }}
          resources: {{- toYaml .Values.initTLSSecret.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: certs
              mountPath: "/certs"
            - name: secret-certs
              mountPath: "/tmp-certs"
      {{- if .Values.volumePermissions.enabled }}
        - name: volume-permissions
          image: {{ include "openldap.volumePermissionsImage" . }}
          imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }}
          command: {{- include "common.tplvalues.render" (dict "value" .Values.volumePermissions.image.command "context" $) | nindent 12 }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          {{- if .Values.volumePermissions.resources }}
          resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }}
          {{- end }}
          volumeMounts:
            - mountPath: /bitnami
              name: data
      {{- end }}
    
      serviceAccountName: {{ template "openldap.serviceAccountName" . }}
      {{- include "openldap.imagePullSecrets" . | nindent 6 }}
      {{- if .Values.hostAliases }}
      hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.affinity }}
      affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }}
      {{- else }}
      affinity:
        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "openldap" "context" $) | nindent 10 }}
        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "openldap" "context" $) | nindent 10 }}
        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
      {{- end }}
      {{- if .Values.nodeSelector }}
      nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
      {{- end }}
      {{- if .Values.schedulerName }}
      schedulerName: {{- .Values.schedulerName | quote }}
      {{- end }}
      {{- if .Values.podSecurityContext.enabled }}
      securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
      {{- end }}
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName | quote }}
      {{- end }}
      {{- if .Values.tolerations }}
      tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: {{ include "openldap.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          {{- if .Values.containerSecurityContext.enabled }}
          securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
          {{- end }}
          env:
            - name: LDAP_EXTRA_SCHEMAS
              value: {{ print "cosine,inetorgperson,nis," (include "openldap.schemaFiles" (dict "context" . )) }}
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            {{- if .Values.extraEnvVars }}
            {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
            {{- end }}
          envFrom:
            {{- if .Values.extraEnvVarsCM }}
            - configMapRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
            {{- end }}
            - configMapRef:
                name: {{ template "openldap.fullname" . }}-env
            {{- if .Values.extraEnvVarsSecret }}
            - secretRef:
                name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
            {{- end }}
            - secretRef:
                name: {{ template "openldap.secretName" . }}
          {{- if .Values.resources }}
          resources: {{- toYaml .Values.resources | nindent 12 }}
          {{- end }}
          ports:
            - name: ldap-port
              containerPort: 1389
            - name: ssl-ldap-port
              containerPort: 1636
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.readinessProbe.successThreshold }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.startupProbe.enabled }}
          startupProbe:
            tcpSocket:
              port: ldap-port
            initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.startupProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }}
            successThreshold: {{ .Values.startupProbe.successThreshold }}
            failureThreshold: {{ .Values.startupProbe.failureThreshold }}
          {{- else if .Values.customStartupProbe }}
          startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
          {{- end }}
          {{- if .Values.lifecycleHooks }}
          lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: data
              mountPath: /bitnami/openldap/
            - name: certs
              mountPath: /opt/bitnami/openldap/certs
            {{- range $file := (include "openldap.builtinSchemaFiles" . | split ",") }}
            - name: replication-acls
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- if .Values.customSchemaFiles}}
            {{- range $file := (include "openldap.customSchemaFiles" . | split ",") }}
            - name: custom-schema-files
              mountPath: /opt/bitnami/openldap/etc/schema/{{ $file }}.ldif
              subPath: {{ $file }}.ldif
            {{- end }}
{{- end }}
{{- if or (.Values.customLdifFiles) (.Values.customLdifCm) }}
            - name: custom-ldif-files
              mountPath: /ldifs/
{{- end }}
{{- range .Values.customFileSets }}
{{- $fs := . }}
{{- range .files }}
            - name: {{ $fs.name }}
              mountPath: {{ $fs.targetPath }}/{{ .filename }}
              subPath: {{ .filename }}
{{- end }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.sidecars }}
{{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }}
{{- end }}
      volumes:
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.existingClaim }}
        - name: data
          persistentVolumeClaim:
            claimName: {{ .Values.persistence.existingClaim }}
{{- end }}
{{- end }}
        - name: cm-replication-acls
          configMap:
            name: {{ template "openldap.fullname" . }}-replication-acls
        - name: replication-acls
          emptyDir:
            medium: Memory

{{- if .Values.customLdifFiles }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customldif
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- else if .Values.customLdifCm }}
        - name: cm-custom-ldif-files
          configMap:
            name: {{ .Values.customLdifCm }}
        - name: custom-ldif-files
          emptyDir:
            medium: Memory
{{- end }}
{{- if .Values.customSchemaFiles }}
        - name: cm-custom-schema-files
          configMap:
            name: {{ template "openldap.fullname" . }}-customschema
        - name: custom-schema-files
          emptyDir:
            medium: Memory
{{- end }}
        - name: certs
          emptyDir:
            medium: Memory
{{- if .Values.initTLSSecret.tls_enabled }}
        - name: secret-certs
          secret:
            secretName: {{ .Values.initTLSSecret.secret }}
{{- else }}
        - name: secret-certs
          emptyDir:
            medium: Memory
{{- end }}
{{- range .Values.customFileSets }}
        - name: {{ .name }}
          configMap:
            name: {{ template "openldap.fullname" $ }}-fs-{{ .name }}
{{- end }}
        {{- if .Values.extraVolumes }}
        {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }}
        {{- end }}
{{- if and (not .Values.persistence.existingClaim)  .Values.persistence.enabled }}
  volumeClaimTemplates:
    - metadata:
        name: data
        annotations:
        {{- range $key, $value := .Values.persistence.annotations }}
          {{ $key }}: {{ $value }}
        {{- end }}
      spec:
        accessModes:
        {{- range .Values.persistence.accessModes }}
          - {{ . | quote }}
        {{- end }}
        resources:
          requests:
            storage: {{ .Values.persistence.size | quote }}
      {{- if .Values.persistence.storageClass }}
      {{- if (eq "-" .Values.persistence.storageClass) }}
        storageClassName: ""
      {{- else }}
        storageClassName: "{{ .Values.persistence.storageClass }}"
      {{- end }}
{{- end }}
{{- else if (not .Values.persistence.enabled) }}
        - name: data
          emptyDir: {}
{{- end }}

this looks good, can we get it onto a branch to test?
i think that statefulset is required if each read only replica will persist their data (which i think they will?).
deployment is more suited to stateless workloads (that don't need persisted data, or that share a single persistent volume e.g. in NFS mode)

@davidfrickert
Copy link
Contributor Author

There is a significantly simpler way of achieving the functionality of a read-only proxy that I was not aware, that is using the "meta" backend (and olcReadOnly that was mentioned here already)
This meta backend essentially makes the OpenLDAP server act as a proxy, so it does not need to replicate!

I will try to submit a PR for review once i have it cleaned up.

@pritchardtw
Copy link

@davidfrickert That generally defeats the purpose of a read replica if you want to scale reads if they are all landing on the primaries.

If you want a read only client I'd configure a client with proper read only permissions.

If you want a read replica I'd set up an openldap server as a consumer.

@jp-gouin
Copy link
Owner

jp-gouin commented Oct 3, 2024

Alright ! after a merge nightmare, you can now try the read-only feature. Let me know what you think

@jp-gouin jp-gouin merged commit 7b0d8ea into jp-gouin:master Oct 9, 2024
4 checks passed
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.

3 participants