update
diff --git a/charts/openproject/templates/NOTES.txt b/charts/openproject/templates/NOTES.txt
new file mode 100644
index 0000000..bae4286
--- /dev/null
+++ b/charts/openproject/templates/NOTES.txt
@@ -0,0 +1,11 @@
+Thank you for installing OpenProject 🎉
+
+{{- if .Values.ingress.enabled }}
+You can access it via http{{ if .Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}{{ .Values.ingress.path }}
+{{- end }}
+
+Summary:
+--------
+OpenProject: {{ .Values.image.tag }}
+PostgreSQL: {{ if .Values.postgresql.bundled }}{{ .Values.postgresql.image.tag }}{{ else }}external{{ end }}
+Memcached: {{ if .Values.memcached.bundled }}{{ .Values.memcached.image.tag }}{{ else }}external{{ end }}
diff --git a/charts/openproject/templates/_helpers.tpl b/charts/openproject/templates/_helpers.tpl
new file mode 100644
index 0000000..0a6e284
--- /dev/null
+++ b/charts/openproject/templates/_helpers.tpl
@@ -0,0 +1,152 @@
+{{/*
+Returns the OpenProject image to be used including the respective registry and image tag.
+*/}}
+{{- define "openproject.image" -}}
+{{ .Values.image.registry }}/{{ .Values.image.repository }}{{ if .Values.image.sha256 }}@sha256:{{ .Values.image.sha256 }}{{ else }}:{{ .Values.image.tag }}{{ end }}
+{{- end -}}
+
+{{/*
+Returns the OpenProject image pull secrets, if any are defined
+*/}}
+{{- define "openproject.imagePullSecrets" -}}
+{{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }}
+imagePullSecrets:
+ {{- range (coalesce .Values.imagePullSecrets .Values.global.imagePullSecrets) }}
+ - name: "{{ . }}"
+ {{- end }}
+{{- end }}
+{{- end -}}
+
+{{/*
+Yields the configured container security context if enabled.
+
+Allows writing to the container file system in development mode
+This way the OpenProject container works without mounted tmp volumes
+which may not work correctly in local development clusters.
+*/}}
+{{- define "openproject.containerSecurityContext" }}
+{{- if .Values.containerSecurityContext.enabled }}
+securityContext:
+ {{-
+ mergeOverwrite
+ (omit .Values.containerSecurityContext "enabled" | deepCopy)
+ (dict "readOnlyRootFilesystem" (and
+ (not .Values.develop)
+ (get .Values.containerSecurityContext "readOnlyRootFilesystem")
+ ))
+ | toYaml
+ | nindent 2
+ }}
+{{- end }}
+{{- end }}
+
+{{/* Yields the configured pod security context if enabled. */}}
+{{- define "openproject.podSecurityContext" }}
+{{- if .Values.podSecurityContext.enabled }}
+securityContext:
+ {{ omit .Values.podSecurityContext "enabled" | toYaml | nindent 2 | trim }}
+{{- end }}
+{{- end }}
+
+
+{{- define "openproject.useTmpVolumes" -}}
+{{- if ne .Values.openproject.useTmpVolumes nil -}}
+ {{- .Values.openproject.useTmpVolumes -}}
+{{- else -}}
+ {{- (not .Values.develop) -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "openproject.tmpVolumeMounts" -}}
+{{- if eq (include "openproject.useTmpVolumes" .) "true" }}
+- mountPath: /tmp
+ name: tmp
+- mountPath: /app/tmp
+ name: app-tmp
+{{- end }}
+{{- end -}}
+
+{{- define "openproject.tmpVolumeSpec" -}}
+{{- if eq (include "openproject.useTmpVolumes" .) "true" }}
+- name: tmp
+ # we can't use emptyDir due to the sticky bit issue
+ # see: https://github.com/kubernetes/kubernetes/issues/110835
+ ephemeral:
+ volumeClaimTemplate:
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ resources:
+ requests:
+ storage: {{ .Values.openproject.tmpVolumesStorage }}
+- name: app-tmp
+ # we can't use emptyDir due to the sticky bit / world writable issue
+ # see: https://github.com/kubernetes/kubernetes/issues/110835
+ ephemeral:
+ volumeClaimTemplate:
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ resources:
+ requests:
+ storage: {{ .Values.openproject.tmpVolumesStorage }}
+{{- end }}
+{{- end -}}
+
+{{- define "openproject.envFrom" -}}
+- secretRef:
+ name: {{ include "common.names.fullname" . }}-core
+{{- if .Values.openproject.oidc.enabled }}
+- secretRef:
+ name: {{ include "common.names.fullname" . }}-oidc
+{{- end }}
+{{- if .Values.s3.enabled }}
+- secretRef:
+ name: {{ include "common.names.fullname" . }}-s3
+{{- end }}
+{{- if eq .Values.openproject.cache.store "memcache" }}
+- secretRef:
+ name: {{ include "common.names.fullname" . }}-memcached
+{{- end }}
+{{- if .Values.environment }}
+- secretRef:
+ name: {{ include "common.names.fullname" . }}-environment
+{{- end }}
+{{- if .Values.openproject.extraEnvVarsSecret }}
+- secretRef:
+ name: {{ .Values.openproject.extraEnvVarsSecret }}
+{{- end }}
+{{- if .Values.openproject.oidc.extraOidcSealedSecret }}
+- secretRef:
+ name: {{ .Values.openproject.oidc.extraOidcSealedSecret }}
+{{- end }}
+{{- end }}
+
+{{- define "openproject.env" -}}
+{{- if .Values.egress.tls.rootCA.fileName }}
+- name: SSL_CERT_FILE
+ value: "/etc/ssl/certs/custom-ca.pem"
+{{- end }}
+{{- if .Values.postgresql.auth.existingSecret }}
+- name: OPENPROJECT_DB_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.postgresql.auth.existingSecret }}
+ key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey }}
+{{- else if .Values.postgresql.auth.password }}
+- name: OPENPROJECT_DB_PASSWORD
+ value: {{ .Values.postgresql.auth.password }}
+{{- else }}
+- name: OPENPROJECT_DB_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: {{ include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" .Values.postgresql "context" $) }}
+ key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey }}
+{{- end }}
+{{- end }}
+
+{{- define "openproject.envChecksums" }}
+# annotate pods with env value checksums so changes trigger re-deployments
+{{/* If I knew how to map and reduce a range in helm I would do that and use a single checksum. But here we are. */}}
+{{- range $suffix := list "core" "memcached" "oidc" "s3" "environment" }}
+checksum/env-{{ $suffix }}: {{ include (print $.Template.BasePath "/secret_" $suffix ".yaml") $ | sha256sum }}
+{{- end }}
+{{- end }}
diff --git a/charts/openproject/templates/ingress.yaml b/charts/openproject/templates/ingress.yaml
new file mode 100644
index 0000000..6695da0
--- /dev/null
+++ b/charts/openproject/templates/ingress.yaml
@@ -0,0 +1,33 @@
+{{- if .Values.ingress.enabled -}}
+---
+apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }}
+kind: Ingress
+metadata:
+ name: {{ include "common.names.fullname" . }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if .Values.ingress.ingressClassName }}
+ ingressClassName: {{ .Values.ingress.ingressClassName }}
+ {{- end }}
+ {{- if .Values.ingress.tls.enabled }}
+ tls:
+ - hosts:
+ - {{ .Values.ingress.host | quote }}
+ secretName: "{{ .Values.ingress.tls.secretName }}"
+ {{- end }}
+ rules:
+ - host: {{ .Values.ingress.host | quote }}
+ http:
+ paths:
+ - path: {{ .Values.ingress.path }}
+ {{- if eq "true" (include "common.ingress.supportsPathType" .) }}
+ pathType: {{ .Values.ingress.pathType }}
+ {{- end }}
+ backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" $) "servicePort" "http" "context" $) | nindent 14 }}
+...
+{{- end }}
diff --git a/charts/openproject/templates/persistentvolumeclaim.yaml b/charts/openproject/templates/persistentvolumeclaim.yaml
new file mode 100644
index 0000000..8350d20
--- /dev/null
+++ b/charts/openproject/templates/persistentvolumeclaim.yaml
@@ -0,0 +1,24 @@
+{{- if .Values.persistence.enabled }}
+{{- if not .Values.persistence.existingClaim }}
+---
+apiVersion: "v1"
+kind: "PersistentVolumeClaim"
+metadata:
+ name: {{ include "common.names.fullname" . }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ {{- with .Values.persistence.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ accessModes: {{ .Values.persistence.accessModes }}
+ {{- if .Values.persistence.storageClassName }}
+ storageClassName: {{ .Values.persistence.storageClassName }}
+ {{- end }}
+ resources:
+ requests:
+ storage: {{ .Values.persistence.size | quote }}
+...
+{{- end }}
+{{- end }}
diff --git a/charts/openproject/templates/secret_core.yaml b/charts/openproject/templates/secret_core.yaml
new file mode 100644
index 0000000..d9eb32a
--- /dev/null
+++ b/charts/openproject/templates/secret_core.yaml
@@ -0,0 +1,31 @@
+---
+apiVersion: "v1"
+kind: "Secret"
+metadata:
+ name: "{{ include "common.names.fullname" . }}-core"
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+stringData:
+ {{- if .Values.postgresql.bundled }}
+ DATABASE_HOST: {{ printf "%s-postgresql.%s.svc.%s" .Release.Name .Release.Namespace .Values.clusterDomain | quote }}
+ DATABASE_PORT: "{{ .Values.postgresql.primary.service.ports.postgresql }}"
+ DATABASE_URL: "postgresql://{{ .Values.postgresql.auth.username }}@{{ include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" .Values.postgresql "context" $) }}:{{ .Values.postgresql.primary.service.ports.postgresql }}/{{ .Values.postgresql.auth.database }}"
+ {{- else }}
+ DATABASE_HOST: "{{ .Values.postgresql.connection.host }}"
+ DATABASE_PORT: "{{ .Values.postgresql.connection.port }}"
+ DATABASE_URL: "postgresql://{{ .Values.postgresql.auth.username }}@{{ .Values.postgresql.connection.host }}:{{ .Values.postgresql.connection.port }}/{{ .Values.postgresql.auth.database }}"
+ {{- end }}
+ OPENPROJECT_SEED_ADMIN_USER_PASSWORD: {{ .Values.openproject.admin_user.password | quote }}
+ OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: {{ .Values.openproject.admin_user.password_reset | quote }}
+ OPENPROJECT_SEED_ADMIN_USER_NAME: {{ .Values.openproject.admin_user.name | quote }}
+ OPENPROJECT_SEED_ADMIN_USER_MAIL: {{ .Values.openproject.admin_user.mail | quote }}
+ OPENPROJECT_HTTPS: {{ (.Values.develop | ternary "false" .Values.openproject.https) | quote }}
+ OPENPROJECT_SEED_LOCALE: {{ .Values.openproject.seed_locale | quote }}
+ {{- if .Values.ingress.enabled }}
+ OPENPROJECT_HOST__NAME: {{ .Values.openproject.host | default .Values.ingress.host | quote }}
+ {{- end }}
+ OPENPROJECT_HSTS: {{ .Values.openproject.hsts | quote }}
+ OPENPROJECT_RAILS__CACHE__STORE: {{ .Values.openproject.cache.store | quote }}
+ OPENPROJECT_RAILS__RELATIVE__URL__ROOT: {{ .Values.openproject.railsRelativeUrlRoot | default "" | quote }}
+ POSTGRES_STATEMENT_TIMEOUT: {{ .Values.openproject.postgresStatementTimeout | quote }}
+...
diff --git a/charts/openproject/templates/secret_environment.yaml b/charts/openproject/templates/secret_environment.yaml
new file mode 100644
index 0000000..ab08a67
--- /dev/null
+++ b/charts/openproject/templates/secret_environment.yaml
@@ -0,0 +1,15 @@
+{{- if .Values.environment }}
+---
+apiVersion: "v1"
+kind: "Secret"
+metadata:
+ name: "{{ include "common.names.fullname" . }}-environment"
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+stringData:
+ # Additional environment variables
+ {{- range $key, $value := .Values.environment }}
+ {{ $key }}: {{ $value | quote }}
+ {{- end }}
+...
+{{- end }}
diff --git a/charts/openproject/templates/secret_memcached.yaml b/charts/openproject/templates/secret_memcached.yaml
new file mode 100644
index 0000000..a6a7dc6
--- /dev/null
+++ b/charts/openproject/templates/secret_memcached.yaml
@@ -0,0 +1,16 @@
+{{- if eq .Values.openproject.cache.store "memcache" }}
+---
+apiVersion: "v1"
+kind: "Secret"
+metadata:
+ name: "{{ include "common.names.fullname" . }}-memcached"
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+stringData:
+ {{- if .Values.memcached.bundled }}
+ OPENPROJECT_CACHE__MEMCACHE__SERVER: "{{ .Release.Name }}-memcached:11211"
+ {{- else }}
+ OPENPROJECT_CACHE__MEMCACHE__SERVER: "{{ .Values.memcached.connection.host }}:{{.Values.memcached.connection.port }}"
+ {{- end }}
+...
+{{- end }}
diff --git a/charts/openproject/templates/secret_oidc.yaml b/charts/openproject/templates/secret_oidc.yaml
new file mode 100644
index 0000000..03a16a8
--- /dev/null
+++ b/charts/openproject/templates/secret_oidc.yaml
@@ -0,0 +1,32 @@
+{{- if .Values.openproject.oidc.enabled }}
+---
+apiVersion: "v1"
+kind: "Secret"
+metadata:
+ name: "{{ include "common.names.fullname" . }}-oidc"
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+stringData:
+ # OpenID Connect settings
+ {{ $oidc_prefix := printf "OPENPROJECT_OPENID__CONNECT_%s" (upper .Values.openproject.oidc.provider) }}
+ {{ $oidc_prefix }}_DISPLAY__NAME: {{ .Values.openproject.oidc.displayName | quote }}
+ {{ $oidc_prefix }}_HOST: {{ .Values.openproject.oidc.host | quote }}
+ {{/* Fall back to '_' as secret name if the name is not given. This way `lookup` will return null (since secrets with this name will and cannot exist) which it doesn't with an empty string. */}}
+ {{ $secret := (lookup "v1" "Secret" .Release.Namespace (default "_" .Values.openproject.oidc.existingSecret)) | default (dict "data" dict) -}}
+ {{ $oidc_prefix }}_IDENTIFIER: {{
+ default .Values.openproject.oidc.identifier (get $secret.data .Values.openproject.oidc.secretKeys.identifier | b64dec) | quote
+ }}
+ {{ $oidc_prefix }}_SECRET: {{
+ default .Values.openproject.oidc.secret (get $secret.data .Values.openproject.oidc.secretKeys.secret | b64dec) | quote
+ }}
+ {{ $oidc_prefix }}_AUTHORIZATION__ENDPOINT: {{ .Values.openproject.oidc.authorizationEndpoint | quote }}
+ {{ $oidc_prefix }}_TOKEN__ENDPOINT: {{ .Values.openproject.oidc.tokenEndpoint | quote }}
+ {{ $oidc_prefix }}_USERINFO__ENDPOINT: {{ .Values.openproject.oidc.userinfoEndpoint | quote }}
+ {{ $oidc_prefix }}_END__SESSION__ENDPOINT: {{ .Values.openproject.oidc.endSessionEndpoint | quote }}
+ {{ $oidc_prefix }}_SCOPE: {{ .Values.openproject.oidc.scope | quote }}
+ {{- range $key, $value := .Values.openproject.oidc.attribute_map }}
+ {{ $mapping_key := printf "%s_ATTRIBUTE__MAP_%s" $oidc_prefix (upper $key) }}
+ {{ $mapping_key }}: {{ $value | quote }}
+ {{- end }}
+...
+{{- end }}
diff --git a/charts/openproject/templates/secret_s3.yaml b/charts/openproject/templates/secret_s3.yaml
new file mode 100644
index 0000000..354b01e
--- /dev/null
+++ b/charts/openproject/templates/secret_s3.yaml
@@ -0,0 +1,38 @@
+{{- if .Values.s3.enabled }}
+---
+apiVersion: "v1"
+kind: "Secret"
+metadata:
+ name: "{{ include "common.names.fullname" . }}-s3"
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+stringData:
+ OPENPROJECT_ATTACHMENTS__STORAGE: fog
+ OPENPROJECT_FOG_CREDENTIALS_PROVIDER: AWS
+ {{/* Fall back to '_' as secret name if the name is not given. This way `lookup` will return null (since secrets with this name will and cannot exist) which it doesn't with an empty string. */}}
+ {{ $secret := (lookup "v1" "Secret" .Release.Namespace (default "_" .Values.s3.auth.existingSecret)) | default (dict "data" dict) -}}
+ OPENPROJECT_FOG_CREDENTIALS_AWS__ACCESS__KEY__ID: {{
+ default .Values.s3.auth.accessKeyId (get $secret.data .Values.s3.auth.secretKeys.accessKeyId | b64dec) | quote
+ }}
+ OPENPROJECT_FOG_CREDENTIALS_AWS__SECRET__ACCESS__KEY: {{
+ default .Values.s3.auth.secretAccessKey (get $secret.data .Values.s3.auth.secretKeys.secretAccessKey | b64dec) | quote
+ }}
+ {{ if .Values.s3.endpoint -}}
+ OPENPROJECT_FOG_CREDENTIALS_ENDPOINT: {{ .Values.s3.endpoint }}
+ {{- end }}
+ {{ if .Values.s3.host -}}
+ OPENPROJECT_FOG_CREDENTIALS_HOST: {{ .Values.s3.host }}
+ {{- end }}
+ {{ if .Values.s3.port -}}
+ OPENPROJECT_FOG_CREDENTIALS_PORT: "{{ .Values.s3.port }}"
+ {{- end }}
+ OPENPROJECT_FOG_DIRECTORY: {{ .Values.s3.bucketName }}
+ OPENPROJECT_FOG_CREDENTIALS_REGION: {{ .Values.s3.region }}
+ OPENPROJECT_FOG_CREDENTIALS_PATH__STYLE: "{{ .Values.s3.pathStyle }}"
+ OPENPROJECT_FOG_CREDENTIALS_AWS__SIGNATURE__VERSION: "{{ .Values.s3.signatureVersion }}"
+ # remove use_iam_profile fallback after some point
+ OPENPROJECT_FOG_CREDENTIALS_USE__IAM__PROFILE: {{ if or .Values.s3.use_iam_profile .Values.s3.useIamProfile }}"true"{{else}}"false"{{end}}
+ OPENPROJECT_FOG_CREDENTIALS_ENABLE__SIGNATURE__V4__STREAMING: {{ if .Values.s3.enableSignatureV4Streaming }}"true"{{else}}"false"{{end}}
+ OPENPROJECT_DIRECT__UPLOADS: {{ if .Values.s3.directUploads }}"true"{{else}}"false"{{end}}
+...
+{{- end }}
diff --git a/charts/openproject/templates/seeder-job.yaml b/charts/openproject/templates/seeder-job.yaml
new file mode 100644
index 0000000..84c0bc7
--- /dev/null
+++ b/charts/openproject/templates/seeder-job.yaml
@@ -0,0 +1,70 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: {{ include "common.names.fullname" . }}-seeder-{{ now | date "20060102150405" }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ {{- with .Values.seederJob.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ ttlSecondsAfterFinished: 6000
+ template:
+ metadata:
+ labels:
+ {{- include "common.labels.standard" . | nindent 8 }}
+ openproject/process: seeder
+ {{- with .Values.seederJob.annotations }}
+ annotations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ spec:
+ {{- include "openproject.imagePullSecrets" . | indent 6 }}
+ {{- include "openproject.podSecurityContext" . | indent 6 }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ volumes:
+ {{- include "openproject.tmpVolumeSpec" . | indent 8 }}
+ {{- if .Values.persistence.enabled }}
+ - name: "data"
+ persistentVolumeClaim:
+ claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{- else }}{{ include "common.names.fullname" . }}{{- end }}
+ {{- end }}
+ initContainers:
+ - name: check-db-ready
+ image: "{{ .Values.initdb.image.registry }}/{{ .Values.initdb.image.repository }}:{{ .Values.initdb.image.tag }}"
+ imagePullPolicy: {{ .Values.initdb.image.imagePullPolicy }}
+ command: [
+ 'sh',
+ '-c',
+ 'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT -U {{ .Values.postgresql.auth.username }}; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done;'
+ ]
+ envFrom:
+ {{- include "openproject.envFrom" . | nindent 12 }}
+ env:
+ {{- include "openproject.env" . | nindent 12 }}
+ resources:
+ {{- toYaml .Values.initdb.resources | nindent 12 }}
+ {{- include "openproject.containerSecurityContext" . | indent 10 }}
+ containers:
+ - name: seeder
+ image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}{{ if .Values.image.sha256 }}@sha256:{{ .Values.image.sha256 }}{{ else }}:{{ .Values.image.tag }}{{ end }}"
+ imagePullPolicy: {{ .Values.image.imagePullPolicy }}
+ args:
+ - bash
+ - /app/docker/prod/seeder
+ envFrom:
+ {{- include "openproject.envFrom" . | nindent 12 }}
+ env:
+ {{- include "openproject.env" . | nindent 12 }}
+ volumeMounts:
+ {{- include "openproject.tmpVolumeMounts" . | indent 12 }}
+ {{- if .Values.persistence.enabled }}
+ - name: "data"
+ mountPath: "/var/openproject/assets"
+ {{- end }}
+ {{- include "openproject.containerSecurityContext" . | indent 10 }}
+ restartPolicy: OnFailure
diff --git a/charts/openproject/templates/service.yaml b/charts/openproject/templates/service.yaml
new file mode 100644
index 0000000..ac23b71
--- /dev/null
+++ b/charts/openproject/templates/service.yaml
@@ -0,0 +1,31 @@
+---
+{{- if or .Values.service.enabled .Values.ingress.enabled }}
+apiVersion: "v1"
+kind: "Service"
+metadata:
+ name: {{ include "common.names.fullname" . }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ {{- if .Values.service.sessionAffinity.enabled }}
+ sessionAffinity: "ClientIP"
+ sessionAffinityConfig:
+ clientIP:
+ timeoutSeconds: {{ .Values.service.sessionAffinity.timeoutSeconds }}
+ {{- end }}
+ ports:
+ {{- range $key, $value := .Values.service.ports }}
+ - port: {{ $value.port }}
+ targetPort: {{ $key }}
+ protocol: {{ $value.protocol }}
+ name: {{ $key }}
+ {{- if and (eq $.Values.service.type "NodePort") $value.nodePort }}
+ nodePort: {{ $value.nodePort }}
+ {{- end }}
+ {{- end }}
+ selector:
+ {{- include "common.labels.matchLabels" . | nindent 4 }}
+ openproject/process: web
+{{- end }}
+...
diff --git a/charts/openproject/templates/serviceaccount.yaml b/charts/openproject/templates/serviceaccount.yaml
new file mode 100644
index 0000000..046ad71
--- /dev/null
+++ b/charts/openproject/templates/serviceaccount.yaml
@@ -0,0 +1,14 @@
+{{- if .Values.serviceAccount.create -}}
+---
+apiVersion: "v1"
+kind: "ServiceAccount"
+metadata:
+ name: {{ include "common.names.fullname" . }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ {{- with .Values.serviceAccount.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+...
+{{- end }}
diff --git a/charts/openproject/templates/tests/test-connection.yaml b/charts/openproject/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..bd63462
--- /dev/null
+++ b/charts/openproject/templates/tests/test-connection.yaml
@@ -0,0 +1,21 @@
+---
+apiVersion: "v1"
+kind: "Pod"
+metadata:
+ name: "{{ include "common.names.fullname" . }}-test-connection"
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+spec:
+ containers:
+ - name: "wget"
+ image: "busybox"
+ command: ['wget']
+ args:
+ - '--no-verbose'
+ - '--tries=1'
+ - '--spider'
+ - '{{ include "common.names.fullname" . }}:{{ .Values.service.ports.http.port }}/health_check'
+ restartPolicy: "Never"
+...
diff --git a/charts/openproject/templates/web-deployment.yaml b/charts/openproject/templates/web-deployment.yaml
new file mode 100644
index 0000000..4918558
--- /dev/null
+++ b/charts/openproject/templates/web-deployment.yaml
@@ -0,0 +1,128 @@
+---
+apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
+kind: Deployment
+metadata:
+ name: {{ include "common.names.fullname" . }}-web
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ openproject/process: web
+spec:
+ replicas: {{ .Values.replicaCount }}
+ strategy:
+ type: {{ .Values.strategy.type }}
+ selector:
+ matchLabels:
+ {{- include "common.labels.matchLabels" . | nindent 6 }}
+ openproject/process: web
+ template:
+ metadata:
+ annotations:
+ {{- range $key, $val := .Values.podAnnotations }}
+ {{ $key }}: {{ $val | quote }}
+ {{- end }}
+ {{- include "openproject.envChecksums" . | nindent 8 }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 8 }}
+ openproject/process: web
+ spec:
+ {{- include "openproject.imagePullSecrets" . | indent 6 }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ {{- include "openproject.podSecurityContext" . | indent 6 }}
+ serviceAccountName: {{ include "common.names.fullname" . }}
+ volumes:
+ {{- include "openproject.tmpVolumeSpec" . | indent 8 }}
+ {{- if .Values.egress.tls.rootCA.fileName }}
+ - name: ca-pemstore
+ configMap:
+ name: "{{- .Values.egress.tls.rootCA.configMap }}"
+ {{- end }}
+ {{- if .Values.persistence.enabled }}
+ - name: "data"
+ persistentVolumeClaim:
+ claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{- else }}{{ include "common.names.fullname" . }}{{- end }}
+ {{- end }}
+ initContainers:
+ - name: wait-for-db
+ {{- include "openproject.containerSecurityContext" . | indent 10 }}
+ image: {{ include "openproject.image" . }}
+ imagePullPolicy: {{ .Values.image.imagePullPolicy }}
+ envFrom:
+ {{- include "openproject.envFrom" . | nindent 12 }}
+ env:
+ {{- include "openproject.env" . | nindent 12 }}
+ command:
+ - bash
+ - /app/docker/prod/wait-for-db
+ containers:
+ - name: "openproject"
+ {{- include "openproject.containerSecurityContext" . | indent 10 }}
+ image: {{ include "openproject.image" . }}
+ imagePullPolicy: {{ .Values.image.imagePullPolicy }}
+ envFrom:
+ {{- include "openproject.envFrom" . | nindent 12 }}
+ env:
+ {{- include "openproject.env" . | nindent 12 }}
+ command:
+ - bash
+ - /app/docker/prod/web
+ volumeMounts:
+ {{- include "openproject.tmpVolumeMounts" . | indent 12 }}
+ {{- if .Values.persistence.enabled }}
+ - name: "data"
+ mountPath: "/var/openproject/assets"
+ {{- end }}
+ {{- if .Values.egress.tls.rootCA.fileName }}
+ - name: ca-pemstore
+ mountPath: /etc/ssl/certs/custom-ca.pem
+ subPath: {{ .Values.egress.tls.rootCA.fileName }}
+ readOnly: false
+ {{- end }}
+ ports:
+ {{- range $key, $value := .Values.service.ports }}
+ - name: {{ $key }}
+ containerPort: {{ $value.containerPort }}
+ protocol: {{ $value.protocol }}
+ {{- end }}
+ {{- if .Values.probes.liveness.enabled }}
+ livenessProbe:
+ httpGet:
+ path: "{{ .Values.openproject.railsRelativeUrlRoot | default "" }}/health_checks/default"
+ port: 8080
+ httpHeaders:
+ # required otherwise health check will return 404 because health check is done using the Pod IP, which may cause issues with downstream variants
+ - name: Host
+ value: localhost
+ initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
+ timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
+ periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
+ failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
+ successThreshold: {{ .Values.probes.liveness.successThreshold }}
+ {{- end }}
+ {{- if .Values.probes.readiness.enabled }}
+ readinessProbe:
+ httpGet:
+ path: "{{ .Values.openproject.railsRelativeUrlRoot | default "" }}/health_checks/default"
+ port: 8080
+ httpHeaders:
+ # required otherwise health check will return 404 because health check is done using the Pod IP, which may cause issues with downstream variants
+ - name: Host
+ value: localhost
+ initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
+ timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
+ periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
+ failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
+ successThreshold: {{ .Values.probes.readiness.successThreshold }}
+ {{- end }}
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
diff --git a/charts/openproject/templates/worker-deployment.yaml b/charts/openproject/templates/worker-deployment.yaml
new file mode 100644
index 0000000..3d28d3d
--- /dev/null
+++ b/charts/openproject/templates/worker-deployment.yaml
@@ -0,0 +1,98 @@
+{{- range $workerName, $workerValues := .Values.workers }}
+{{- with $ -}}
+---
+apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }}
+kind: Deployment
+metadata:
+ name: {{ include "common.names.fullname" . }}-worker-{{ $workerName }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 4 }}
+ openproject/process: worker-{{ $workerName }}
+spec:
+ replicas: {{( kindIs "invalid" $workerValues.replicaCount) | ternary .Values.backgroundReplicaCount $workerValues.replicaCount }}
+ strategy:
+ {{ coalesce $workerValues.strategy .Values.strategy | toYaml | nindent 4 }}
+ selector:
+ matchLabels:
+ {{- include "common.labels.matchLabels" . | nindent 6 }}
+ openproject/process: worker-{{ $workerName }}
+ template:
+ metadata:
+ annotations:
+ {{- range $key, $val := .Values.podAnnotations }}
+ {{ $key }}: {{ $val | quote }}
+ {{- end }}
+ {{- include "openproject.envChecksums" . | nindent 8 }}
+ labels:
+ {{- include "common.labels.standard" . | nindent 8 }}
+ openproject/process: worker-{{ $workerName }}
+ spec:
+ {{- include "openproject.imagePullSecrets" . | indent 6 }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{ toYaml . | nindent 8 | trim }}
+ {{- end }}
+ {{- include "openproject.podSecurityContext" . | indent 6 }}
+ serviceAccountName: {{ include "common.names.fullname" . }}
+ volumes:
+ {{- include "openproject.tmpVolumeSpec" . | indent 8 }}
+ {{- if .Values.egress.tls.rootCA.fileName }}
+ - name: ca-pemstore
+ configMap:
+ name: "{{- .Values.egress.tls.rootCA.configMap }}"
+ {{- end }}
+ {{- if .Values.persistence.enabled }}
+ - name: "data"
+ persistentVolumeClaim:
+ claimName: {{ include "common.names.fullname" . }}
+ {{- end }}
+ initContainers:
+ - name: wait-for-db
+ {{- include "openproject.containerSecurityContext" . | indent 10 }}
+ image: {{ include "openproject.image" . }}
+ imagePullPolicy: {{ .Values.image.imagePullPolicy }}
+ envFrom:
+ {{- include "openproject.envFrom" . | nindent 12 }}
+ env:
+ {{- include "openproject.env" . | nindent 12 }}
+ command:
+ - bash
+ - /app/docker/prod/wait-for-db
+ containers:
+ - name: "openproject"
+ {{- include "openproject.containerSecurityContext" . | indent 10 }}
+ image: {{ include "openproject.image" . }}
+ imagePullPolicy: {{ .Values.image.imagePullPolicy }}
+ envFrom:
+ {{- include "openproject.envFrom" . | nindent 12 }}
+ command:
+ - bash
+ - /app/docker/prod/worker
+ env:
+ {{- include "openproject.env" . | nindent 12 }}
+ - name: "QUEUE"
+ value: "{{ $workerValues.queues }}"
+ volumeMounts:
+ {{- include "openproject.tmpVolumeMounts" . | indent 12 }}
+ {{- if .Values.persistence.enabled }}
+ - name: "data"
+ mountPath: "/var/openproject/assets"
+ {{- end }}
+ {{- if .Values.egress.tls.rootCA.fileName }}
+ - name: ca-pemstore
+ mountPath: /etc/ssl/certs/custom-ca.pem
+ subPath: {{ .Values.egress.tls.rootCA.fileName }}
+ readOnly: false
+ {{- end }}
+ resources:
+ {{- coalesce $workerValues.resources .Values.resources | toYaml | nindent 12 }}
+{{- end }}
+{{ end }}
\ No newline at end of file