diff --git a/.changeset/moody-papayas-tie.md b/.changeset/moody-papayas-tie.md new file mode 100644 index 0000000..85db71d --- /dev/null +++ b/.changeset/moody-papayas-tie.md @@ -0,0 +1,5 @@ +--- +"@openproject/helm-charts": minor +--- + +Add support for the cron-based service for incoming email check via IMAP diff --git a/charts/openproject/README.md b/charts/openproject/README.md index 392a231..f2f9edf 100644 --- a/charts/openproject/README.md +++ b/charts/openproject/README.md @@ -126,7 +126,7 @@ persistence: s3: enabled: true accessKeyId: - # host: + # host: # port: ``` @@ -283,6 +283,9 @@ type: Opaque ``` To add the actual content, you can simply add `stringData:` to the end of it and save it. +Alternatively you can create the secret in one line as well via the `--from-literal` option. + +**Secret keys** The keys which are looked up inside the secret data can be changed from their defaults in the values as well. This is the same in all cases where next to `existingSecret` you can also set `secretKeys`. @@ -296,6 +299,15 @@ stringData: password: userPassword ``` +Here an example how to do the same using the `--from-literal` option. +We won't give these examples for the other sections below but it works just the same. + +```bash +kubectl -n openproject create secret generic db-credentials \ + --from-literal=postgres-password=postgresPassword \ + --from-literal=password=userPassword +``` + If you have an existing secret where the keys are not `postgres-password` and `password`, you can customize the used keys as mentioned above. For instance: @@ -307,7 +319,7 @@ helm upgrade --create-namespace --namespace openproject --install openproject \ --set postgresql.auth.secretKeys.userPasswordKey=userpw ``` -This can be customized for the the credentials in the following sections too in the same fashion. +This can also be customized for the the credentials in the following sections in the same fashion. You can look up the respective options in the [`values.yaml`](./values.yaml) file. #### Default passwords @@ -348,6 +360,14 @@ stringData: secretAccessKey: zwH7t0H3bJQf/TvlQpE7/Y59k9hD+nYNRlKUBpuq ``` +### Incoming E-Mails cron job (IMAP) + +```yaml +stringData: + imapUsername: inbox@mailprovider.com + imapPassword: t*$SFdD*RfahVTnoDr&Caw96FJuU +``` + ## OpenShift For OpenProject to work in OpenShift without further adjustments, diff --git a/charts/openproject/templates/cron-deployment.yaml b/charts/openproject/templates/cron-deployment.yaml new file mode 100644 index 0000000..a0266a9 --- /dev/null +++ b/charts/openproject/templates/cron-deployment.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }} +kind: Deployment +metadata: + name: {{ include "common.names.fullname" . }}-cron + labels: + {{- include "common.labels.standard" . | nindent 4 }} + openproject/process: cron +spec: + replicas: {{ if .Values.cron.enabled }}{{- 1 }}{{ else }}{{- 0 }}{{ end }} + strategy: + type: "Recreate" + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + openproject/process: cron + template: + metadata: + annotations: + {{- range $key, $val := .Values.podAnnotations }} + {{ $key }}: {{ $val | quote }} + {{- end }} + {{- include "openproject.envChecksums" . | nindent 8 }} + checksum/env-cron-environment: {{ include (print $.Template.BasePath "/secret_cron_environment.yaml") $ | sha256sum }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + openproject/process: cron + 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 }} + {{- include "openproject.extraVolumes" . | indent 8 }} + initContainers: + - name: wait-for-db + {{- include "openproject.containerSecurityContext" . | indent 10 }} + image: {{ include "openproject.image" . }} + imagePullPolicy: {{ .Values.image.imagePullPolicy }} + envFrom: + {{- include "openproject.envFrom" . | nindent 12 }} + - secretRef: + name: {{ include "common.names.fullname" . }}-cron-environment + env: + {{- include "openproject.env" . | nindent 12 }} + command: + - bash + - /app/docker/prod/wait-for-db + resources: + {{- toYaml .Values.appInit.resources | nindent 12 }} + containers: + - name: "cron" + {{- include "openproject.containerSecurityContext" . | indent 10 }} + image: {{ include "openproject.image" . }} + imagePullPolicy: {{ .Values.image.imagePullPolicy }} + envFrom: + {{- include "openproject.envFrom" . | nindent 12 }} + - secretRef: + name: {{ include "common.names.fullname" . }}-cron-environment + command: + - bash + - /app/docker/prod/cron + env: + {{- include "openproject.env" . | nindent 12 }} + 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 }} + {{- include "openproject.extraVolumeMounts" . | indent 12 }} + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/openproject/templates/secret_cron_environment.yaml b/charts/openproject/templates/secret_cron_environment.yaml new file mode 100644 index 0000000..bac3a31 --- /dev/null +++ b/charts/openproject/templates/secret_cron_environment.yaml @@ -0,0 +1,23 @@ +{{- if .Values.cron.environment }} +--- +apiVersion: "v1" +kind: "Secret" +metadata: + name: "{{ include "common.names.fullname" . }}-cron-environment" + labels: + {{- include "common.labels.standard" . | nindent 4 }} +data: # reset data to make sure only keys defined below remain +stringData: + # Additional environment variables + {{- range $key, $value := omit .Values.cron.environment "IMAP_USERNAME" "IMAP_PASSWORD" }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{ $secret := (lookup "v1" "Secret" .Release.Namespace (default "_" .Values.cron.existingSecret)) | default (dict "data" dict) -}} + IMAP_USERNAME: {{ + default .Values.cron.environment.IMAP_USERNAME (get $secret.data .Values.cron.secretKeys.imapUsername | b64dec) | quote + }} + IMAP_PASSWORD: {{ + default .Values.cron.environment.IMAP_PASSWORD (get $secret.data .Values.cron.secretKeys.imapPassword | b64dec) | quote + }} +... +{{- end }} diff --git a/charts/openproject/values.yaml b/charts/openproject/values.yaml index 7ecf3d4..e85adb6 100644 --- a/charts/openproject/values.yaml +++ b/charts/openproject/values.yaml @@ -266,6 +266,27 @@ strategy: # maxSurge: 30% # maxUnavailable: 30% +## Cron job running the incoming email [1] task. +## +## Ref: https://www.openproject.org/docs/installation-and-operations/configuration/incoming-emails/ +cron: + enabled: false + ## See documentation referenced above for all variables. + environment: + IMAP_HOST: + IMAP_USERNAME: + IMAP_PASSWORD: + IMAP_PORT: 993 + + ## To avoid having sensitive credentials in your values.yaml, the preferred way is to + ## use an existing secret containing the IMAP credentials. + ## Specify the name of this existing secret here. + existingSecret: + ## In case your secret does not use the default keys in the secret, you can adjust them here. + secretKeys: + imapUsername: imapUsername + imapPassword: imapPassword + # Define the workers to run, their queues, replicas, strategy, and resources workers: default: diff --git a/spec/charts/openproject/cron_spec.rb b/spec/charts/openproject/cron_spec.rb new file mode 100644 index 0000000..0e46acd --- /dev/null +++ b/spec/charts/openproject/cron_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'oidc configuration' do + let(:default_values) { {} } + let(:template) { HelmTemplate.new(default_values) } + + let(:cron_definition) do + { + 'Deployment/optest-openproject-cron' => 'cron' + } + end + + let(:cron_secret_name) { 'optest-openproject-cron-environment' } + + let(:general_definitions) do + { + 'Deployment/optest-openproject-web' => 'openproject', + 'Deployment/optest-openproject-worker-default' => 'openproject', + /optest-openproject-seeder/ => 'seeder' + } + end + + let(:replicas) do + template.dig 'Deployment/optest-openproject-cron', 'spec', 'replicas' + end + + it 'adds a secret ref to the cron container' do + ref = template.secret_ref cron_definition.keys.first, cron_definition.values.first, cron_secret_name + + expect(Hash(ref).dig('secretRef', 'name')).to eq cron_secret_name + end + + it 'does not add a secret ref to the other containers' do + general_definitions.each do |item, container| + expect(template.secret_ref(item, container, cron_secret_name)).to be_nil + end + end + + context 'with cron.enabled=false (default)' do + it 'does not schedule a cron container', :aggregate_failures do + expect(replicas).to eq 0 + end + end + + context 'with cron.enabled=true' do + let(:default_values) do + HelmTemplate.with_defaults(' + cron: + enabled: true + ') + end + + it 'does schedule a cron container', :aggregate_failures do + expect(replicas).to eq 1 + end + end + + describe 'cron environment secret' do + let(:cron_secret) do + template.dig('Secret/optest-openproject-cron-environment', 'stringData') + end + + let(:expected_keys) do + %w[IMAP_HOST IMAP_PORT IMAP_USERNAME IMAP_PASSWORD] + end + + context 'without an existing secret for the credentials configured' do + let(:default_values) do + HelmTemplate.with_defaults(' + cron: + enabled: true + ') + end + + it 'contains the correct env variables', :aggregate_failures do + expect(cron_secret.keys).to contain_exactly(*expected_keys) + end + end + + context 'with an existing secret for the credentials configured' do + let(:default_values) do + HelmTemplate.with_defaults(' + cron: + enabled: true + existingSecret: imap-credentials + ') + end + + it 'contains the correct env variables', :aggregate_failures do + expect(cron_secret.keys).to contain_exactly(*expected_keys) + end + end + end +end