From 8eaf21435b85593f67d90310866c3dcadb1fd6f2 Mon Sep 17 00:00:00 2001 From: Wenbin Li Date: Mon, 27 Jul 2026 11:01:30 -0700 Subject: [PATCH 1/3] feat: add DOI Helm chart --- doi/README.md | 26 +++ doi/helm/.helmignore | 7 + doi/helm/Chart.yaml | 13 ++ doi/helm/README.md | 84 +++++++++ doi/helm/config/cadc-log.properties | 3 + doi/helm/config/cadc-registry.properties | 21 +++ doi/helm/config/catalina.properties | 10 + doi/helm/config/doi.properties | 19 ++ doi/helm/config/war-rename.conf | 3 + doi/helm/examples/values.example.yaml | 42 +++++ doi/helm/templates/NOTES.txt | 13 ++ doi/helm/templates/_helpers.tpl | 58 ++++++ doi/helm/templates/configmap.yaml | 9 + doi/helm/templates/deployment.yaml | 154 ++++++++++++++++ doi/helm/templates/ingress.yaml | 32 ++++ doi/helm/templates/service.yaml | 17 ++ doi/helm/templates/serviceaccount.yaml | 14 ++ doi/helm/templates/tests/test-connection.yaml | 16 ++ doi/helm/values.yaml | 174 ++++++++++++++++++ 19 files changed, 715 insertions(+) create mode 100644 doi/helm/.helmignore create mode 100644 doi/helm/Chart.yaml create mode 100644 doi/helm/README.md create mode 100644 doi/helm/config/cadc-log.properties create mode 100644 doi/helm/config/cadc-registry.properties create mode 100644 doi/helm/config/catalina.properties create mode 100644 doi/helm/config/doi.properties create mode 100644 doi/helm/config/war-rename.conf create mode 100644 doi/helm/examples/values.example.yaml create mode 100644 doi/helm/templates/NOTES.txt create mode 100644 doi/helm/templates/_helpers.tpl create mode 100644 doi/helm/templates/configmap.yaml create mode 100644 doi/helm/templates/deployment.yaml create mode 100644 doi/helm/templates/ingress.yaml create mode 100644 doi/helm/templates/service.yaml create mode 100644 doi/helm/templates/serviceaccount.yaml create mode 100644 doi/helm/templates/tests/test-connection.yaml create mode 100644 doi/helm/values.yaml diff --git a/doi/README.md b/doi/README.md index 0c41726..4171faf 100644 --- a/doi/README.md +++ b/doi/README.md @@ -114,6 +114,32 @@ docker run --rm -it doi:latest /bin/bash docker run --rm --user tomcat:tomcat --volume=/path/to/external/config:/config:ro --name doi doi:latest ``` +## Kubernetes deployment + +A Helm chart for deploying the DOI service is provided in `doi/helm` from the +repository root. Start with `doi/helm/examples/values.example.yaml`, provide +the environment-specific registry, service, VOSpace, and DataCite settings, +and create the referenced credential and certificate Secrets out of band. + +Validate the chart before installation: + +``` +helm lint doi/helm \ + --set deployment.doi.config.accountPrefix=10.5072 +helm template doi doi/helm \ + --namespace doi \ + --set deployment.doi.config.accountPrefix=10.5072 +``` + +Install it with environment-specific values: + +``` +helm upgrade --install doi doi/helm \ + --namespace doi \ + --create-namespace \ + --values +``` + ## running it with alternative settings ``` docker run --rm --user tomcat:tomcat --volume=/path/to/external/config:/config:ro --name doi-alt doi:latest diff --git a/doi/helm/.helmignore b/doi/helm/.helmignore new file mode 100644 index 0000000..9288868 --- /dev/null +++ b/doi/helm/.helmignore @@ -0,0 +1,7 @@ +# Patterns to ignore when building packages. +.DS_Store +.git/ +.gitignore +.helmignore +*.swp +*.tmp diff --git a/doi/helm/Chart.yaml b/doi/helm/Chart.yaml new file mode 100644 index 0000000..9b9898b --- /dev/null +++ b/doi/helm/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: doi +description: "A Helm chart to install the Digital Object Identifier service" + +maintainers: + - name: Canadian Astronomy Data Centre + email: cadc@nrc-cnrc.gc.ca + +type: application + +version: 0.1.0 + +appVersion: "1.2.0" diff --git a/doi/helm/README.md b/doi/helm/README.md new file mode 100644 index 0000000..ffe482d --- /dev/null +++ b/doi/helm/README.md @@ -0,0 +1,84 @@ +# doi + +Digital Object Identifier service Helm chart. + +This chart deploys the CADC DOI Tomcat service. Non-secret configuration is rendered into a ConfigMap under `/config`; DataCite credentials and the required `doiadmin.pem` and `cadcproxy.pem` files are read from Kubernetes Secrets and merged into the runtime config by an init container. + +## Required Secrets + +Create a Secret for DataCite credentials: + +```shell +kubectl create secret generic doi-datacite \ + --from-literal=username='' \ + --from-literal=password='' +``` + +Create a Secret for service certificates: + +```shell +kubectl create secret generic doi-certs \ + --from-file=doiadmin.pem=./doiadmin.pem \ + --from-file=cadcproxy.pem=./cadcproxy.pem +``` + +Set: + +```yaml +deployment: + doi: + datacite: + auth: + existingSecret: doi-datacite + certificates: + existingSecret: doi-certs +``` + +## Example Values + +Start from `examples/values.example.yaml` and replace the example hostnames, registry IDs, VOSpace URI, DataCite account prefix, and Secret names. + +## Test the Chart + +Render and lint the chart with non-secret placeholder Secret names: + +```shell +helm lint doi/helm \ + --set deployment.doi.datacite.auth.existingSecret=doi-datacite \ + --set deployment.doi.certificates.existingSecret=doi-certs \ + --set deployment.doi.config.accountPrefix=10.5072 + +helm template doi doi/helm \ + --namespace doi \ + --set deployment.doi.datacite.auth.existingSecret=doi-datacite \ + --set deployment.doi.certificates.existingSecret=doi-certs \ + --set deployment.doi.config.accountPrefix=10.5072 +``` + +Dry-run against a cluster: + +```shell +helm upgrade --install doi doi/helm \ + --namespace doi \ + --create-namespace \ + --values doi/helm/examples/values.example.yaml \ + --dry-run +``` + +Install after replacing the example values and creating the required Secrets: + +```shell +helm upgrade --install doi doi/helm \ + --namespace doi \ + --create-namespace \ + --values +``` + +Check the workload: + +```shell +kubectl -n doi get pods +kubectl -n doi logs deploy/doi-tomcat +kubectl -n doi port-forward svc/doi-tomcat-svc 18080:8080 +curl http://localhost:18080/doi/availability +``` diff --git a/doi/helm/config/cadc-log.properties b/doi/helm/config/cadc-log.properties new file mode 100644 index 0000000..f02879e --- /dev/null +++ b/doi/helm/config/cadc-log.properties @@ -0,0 +1,3 @@ +{{- range $val := .Values.deployment.doi.loggingGroups }} +group = {{ $val }} +{{- end }} diff --git a/doi/helm/config/cadc-registry.properties b/doi/helm/config/cadc-registry.properties new file mode 100644 index 0000000..a14245f --- /dev/null +++ b/doi/helm/config/cadc-registry.properties @@ -0,0 +1,21 @@ +# +# local authority map +# +{{- with .Values.deployment.doi.gmsID }} +ivo://ivoa.net/std/GMS#search-1.0 = {{ . }} +ivo://ivoa.net/std/GMS#search-0.1 = {{ . }} +ivo://ivoa.net/std/GMS#users-1.0 = {{ . }} +ivo://ivoa.net/std/UMS#users-0.1 = {{ . }} +ivo://ivoa.net/std/UMS#users-1.0 = {{ . }} +ivo://ivoa.net/sso#tls-with-password = {{ . }} +{{- end }} +{{- with .Values.deployment.doi.oidcURI }} +ivo://ivoa.net/sso#OAuth = {{ . }} +ivo://ivoa.net/sso#OpenID = {{ . }} +{{- end }} + +{{- $raw := .Values.deployment.doi.registryURL -}} +{{- $urls := ternary (list $raw) $raw (kindIs "string" $raw) -}} +{{- range $urls }} +ca.nrc.cadc.reg.client.RegistryClient.baseURL = {{ . }} +{{- end }} diff --git a/doi/helm/config/catalina.properties b/doi/helm/config/catalina.properties new file mode 100644 index 0000000..58a6a0a --- /dev/null +++ b/doi/helm/config/catalina.properties @@ -0,0 +1,10 @@ +tomcat.connector.connectionTimeout=180000 +tomcat.connector.keepAliveTimeout=180000 +tomcat.connector.secure=true +tomcat.connector.scheme=https +tomcat.connector.proxyName={{ .Values.deployment.hostname }} +tomcat.connector.proxyPort=443 +ca.nrc.cadc.auth.PrincipalExtractor.enableClientCertHeader=true +ca.nrc.cadc.util.Log4jInit.messageOnly=true +# (default: ca.nrc.cadc.auth.NoOpIdentityManager) +ca.nrc.cadc.auth.IdentityManager={{ .Values.deployment.doi.identityManagerClass }} diff --git a/doi/helm/config/doi.properties b/doi/helm/config/doi.properties new file mode 100644 index 0000000..a9e7abb --- /dev/null +++ b/doi/helm/config/doi.properties @@ -0,0 +1,19 @@ +ca.nrc.cadc.doi.vospaceParentUri = {{ .Values.deployment.doi.config.vospaceParentUri | required ".Values.deployment.doi.config.vospaceParentUri is required" }} +ca.nrc.cadc.doi.metaDataPrefix = {{ .Values.deployment.doi.config.metaDataPrefix | required ".Values.deployment.doi.config.metaDataPrefix is required" }} +ca.nrc.cadc.doi.groupPrefix = {{ .Values.deployment.doi.config.groupPrefix | required ".Values.deployment.doi.config.groupPrefix is required" }} +ca.nrc.cadc.doi.landingUrl = {{ .Values.deployment.doi.config.landingUrl | required ".Values.deployment.doi.config.landingUrl is required" }} +ca.nrc.cadc.doi.datacite.mdsUrl = {{ .Values.deployment.doi.config.mdsUrl | required ".Values.deployment.doi.config.mdsUrl is required" }} +ca.nrc.cadc.doi.datacite.accountPrefix = {{ .Values.deployment.doi.config.accountPrefix | required ".Values.deployment.doi.config.accountPrefix is required" }} +{{- with .Values.deployment.doi.config.doiIdentifierPrefix }} +ca.nrc.cadc.doi.doiIdentifierPrefix = {{ . }} +{{- end }} +{{- with .Values.deployment.doi.config.publisherGroupURI }} +ca.nrc.cadc.doi.publisherGroupURI = {{ . }} +{{- end }} +{{- with .Values.deployment.doi.config.selfPublish }} +ca.nrc.cadc.doi.selfPublish = {{ . }} +{{- end }} +{{- if .Values.deployment.doi.config.randomTestID }} +ca.nrc.cadc.doi.randomTestID = true +{{- end }} +# ca.nrc.cadc.doi.datacite.username and ca.nrc.cadc.doi.datacite.password are appended at pod startup from Secret {{ include "doi.dataciteAuthSecretName" . }}. diff --git a/doi/helm/config/war-rename.conf b/doi/helm/config/war-rename.conf new file mode 100644 index 0000000..1cfbd1d --- /dev/null +++ b/doi/helm/config/war-rename.conf @@ -0,0 +1,3 @@ +{{- if and .Values.deployment.doi.applicationName (ne .Values.deployment.doi.applicationName "doi") }} +mv doi.war {{ .Values.deployment.doi.applicationName }}.war +{{- end }} diff --git a/doi/helm/examples/values.example.yaml b/doi/helm/examples/values.example.yaml new file mode 100644 index 0000000..eff3354 --- /dev/null +++ b/doi/helm/examples/values.example.yaml @@ -0,0 +1,42 @@ +# Example values for deploying the DOI service. +# +# Copy this file and replace the example values with your environment-specific +# hostnames, registry IDs, VOSpace URI, DataCite account, and Secret names. + +image: + repository: bucket.canfar.net/doi + tag: "1.2.0" + +imagePullSecrets: + - name: bucket-registry-auth + +deployment: + hostname: doi.example.org + doi: + registryURL: https://doi.example.org/reg + gmsID: ivo://example.org/gms + oidcURI: https://iam.example.org/ + + config: + vospaceParentUri: vos://example.org~arc/doi + metaDataPrefix: doi + groupPrefix: doi + landingUrl: https://doi.example.org/doi + mdsUrl: https://mds.datacite.org + accountPrefix: "10.5072" + + datacite: + auth: + existingSecret: doi-datacite + + certificates: + existingSecret: doi-certs + +ingress: + enabled: true + className: traefik + hosts: + - host: doi.example.org + paths: + - path: /doi + pathType: Prefix diff --git a/doi/helm/templates/NOTES.txt b/doi/helm/templates/NOTES.txt new file mode 100644 index 0000000..b3c90f0 --- /dev/null +++ b/doi/helm/templates/NOTES.txt @@ -0,0 +1,13 @@ +'{{ .Chart.Name }}' installed successfully. You can monitor it in the {{ .Release.Namespace }} Namespace: + +kubectl -n {{ .Release.Namespace }} get pods + +Your release is named {{ .Release.Name }}. + +The DOI service expects DataCite credentials from Secret {{ include "doi.dataciteAuthSecretName" . }} +and PEM certificates from Secret {{ include "doi.certificateSecretName" . }}. + +To learn more about the release, try: + + $ helm -n {{ .Release.Namespace }} status {{ .Release.Name }} + $ helm -n {{ .Release.Namespace }} get all {{ .Release.Name }} diff --git a/doi/helm/templates/_helpers.tpl b/doi/helm/templates/_helpers.tpl new file mode 100644 index 0000000..7808780 --- /dev/null +++ b/doi/helm/templates/_helpers.tpl @@ -0,0 +1,58 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "doi.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "doi.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "doi.selectorLabels" -}} +app.kubernetes.io/name: {{ include "doi.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "doi.labels" -}} +helm.sh/chart: {{ include "doi.chart" . }} +{{ include "doi.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Create the name of the service account to use. +*/}} +{{- define "doi.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (printf "%s-service-account" .Release.Name) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +DataCite credential Secret name. +*/}} +{{- define "doi.dataciteAuthSecretName" -}} +{{- required "deployment.doi.datacite.auth.existingSecret is required" .Values.deployment.doi.datacite.auth.existingSecret -}} +{{- end -}} + +{{/* +PEM certificate Secret name. +*/}} +{{- define "doi.certificateSecretName" -}} +{{- required "deployment.doi.certificates.existingSecret is required" .Values.deployment.doi.certificates.existingSecret -}} +{{- end -}} diff --git a/doi/helm/templates/configmap.yaml b/doi/helm/templates/configmap.yaml new file mode 100644 index 0000000..d24c04b --- /dev/null +++ b/doi/helm/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "doi.labels" . | nindent 4 }} +data: +{{ tpl (.Files.Glob "config/*").AsConfig . | indent 2 }} diff --git a/doi/helm/templates/deployment.yaml b/doi/helm/templates/deployment.yaml new file mode 100644 index 0000000..ddc17ab --- /dev/null +++ b/doi/helm/templates/deployment.yaml @@ -0,0 +1,154 @@ +{{- $containerPort := .Values.service.port -}} +{{- $auth := .Values.deployment.doi.datacite.auth | default dict -}} +{{- $authKeys := $auth.secretKeys | default dict -}} +{{- $usernameKey := $authKeys.username | default "username" -}} +{{- $passwordKey := $authKeys.password | default "password" -}} +{{- $certs := .Values.deployment.doi.certificates | default dict -}} +{{- $certKeys := $certs.secretKeys | default dict -}} +{{- $doiadminKey := $certKeys.doiadmin | default "doiadmin.pem" -}} +{{- $cadcproxyKey := $certKeys.cadcproxy | default "cadcproxy.pem" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + run: {{ .Release.Name }}-tomcat + {{- include "doi.labels" . | nindent 4 }} + name: {{ .Release.Name }}-tomcat + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ default 1 .Values.replicaCount }} + selector: + matchLabels: + run: {{ .Release.Name }}-tomcat + template: + metadata: + labels: + run: {{ .Release.Name }}-tomcat + {{- include "doi.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "doi.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automount }} + {{- with .Values.deployment.doi.nodeAffinity }} + affinity: + nodeAffinity: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- $podSC := merge (.Values.volumeInit.podSecurityContext | default dict) (.Values.podSecurityContext | default dict) }} + {{- if not (empty $podSC) }} + securityContext: + {{- toYaml $podSC | nindent 8 }} + {{- end }} + initContainers: + - name: merge-doi-config + image: {{ .Values.volumeInit.image | quote }} + imagePullPolicy: {{ .Values.volumeInit.imagePullPolicy }} + command: ['sh', '-c'] + args: + - | + set -e + cp -R /config-src/. /config-dst/ + cp "/certs/${DOIADMIN_KEY}" /config-dst/doiadmin.pem + cp "/certs/${CADCPROXY_KEY}" /config-dst/cadcproxy.pem + USER=$(tr -d '\n' < "/datacite/${USERNAME_KEY}") + PASS=$(tr -d '\n' < "/datacite/${PASSWORD_KEY}") + printf '\nca.nrc.cadc.doi.datacite.username = %s\n' "$USER" >> /config-dst/doi.properties + printf 'ca.nrc.cadc.doi.datacite.password = %s\n' "$PASS" >> /config-dst/doi.properties + env: + - name: USERNAME_KEY + value: {{ $usernameKey | quote }} + - name: PASSWORD_KEY + value: {{ $passwordKey | quote }} + - name: DOIADMIN_KEY + value: {{ $doiadminKey | quote }} + - name: CADCPROXY_KEY + value: {{ $cadcproxyKey | quote }} + volumeMounts: + - name: config-source + mountPath: /config-src + readOnly: true + - name: datacite-secret + mountPath: /datacite + readOnly: true + - name: certificate-secret + mountPath: /certs + readOnly: true + - name: config-merged + mountPath: /config-dst + {{- with .Values.volumeInit.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + containers: + - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + name: {{ .Release.Name }}-tomcat + resources: + requests: + memory: {{ .Values.deployment.doi.resources.requests.memory }} + cpu: {{ .Values.deployment.doi.resources.requests.cpu }} + limits: + memory: {{ .Values.deployment.doi.resources.limits.memory }} + cpu: {{ .Values.deployment.doi.resources.limits.cpu }} + securityContext: + {{- $tomcatSC := merge (dict "allowPrivilegeEscalation" false) (.Values.securityContext | default dict) }} + {{- toYaml $tomcatSC | nindent 12 }} + {{- with .Values.deployment.doi.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ $containerPort }} + protocol: TCP + {{- with .Values.deployment.doi.extraPorts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: "/config" + name: config-merged + {{- with .Values.deployment.doi.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.startupProbe }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.deployment.extraHosts }} + hostAliases: + {{- range $extraHost := . }} + - ip: {{ $extraHost.ip }} + hostnames: + - {{ $extraHost.hostname }} + {{- end }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config-source + configMap: + name: {{ .Release.Name }}-config + - name: datacite-secret + secret: + secretName: {{ include "doi.dataciteAuthSecretName" . }} + - name: certificate-secret + secret: + secretName: {{ include "doi.certificateSecretName" . }} + - name: config-merged + emptyDir: {} + {{- with .Values.deployment.doi.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/doi/helm/templates/ingress.yaml b/doi/helm/templates/ingress.yaml new file mode 100644 index 0000000..31fbc3e --- /dev/null +++ b/doi/helm/templates/ingress.yaml @@ -0,0 +1,32 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-ingress + namespace: {{ .Release.Namespace }} + labels: + {{- include "doi.labels" . | nindent 4 }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- with .Values.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-tomcat-svc + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/doi/helm/templates/service.yaml b/doi/helm/templates/service.yaml new file mode 100644 index 0000000..7774861 --- /dev/null +++ b/doi/helm/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-tomcat-svc + namespace: {{ .Release.Namespace }} + labels: + run: {{ .Release.Name }}-tomcat-svc + {{- include "doi.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + name: http + protocol: TCP + selector: + run: {{ .Release.Name }}-tomcat diff --git a/doi/helm/templates/serviceaccount.yaml b/doi/helm/templates/serviceaccount.yaml new file mode 100644 index 0000000..7416370 --- /dev/null +++ b/doi/helm/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "doi.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "doi.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/doi/helm/templates/tests/test-connection.yaml b/doi/helm/templates/tests/test-connection.yaml new file mode 100644 index 0000000..7ff4100 --- /dev/null +++ b/doi/helm/templates/tests/test-connection.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-test-connection" + namespace: {{ .Release.Namespace }} + labels: + {{- include "doi.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox:1.36 + command: ['wget'] + args: ['{{ .Release.Name }}-tomcat-svc:{{ .Values.service.port }}{{ .Values.deployment.doi.availabilityPath }}'] + restartPolicy: Never diff --git a/doi/helm/values.yaml b/doi/helm/values.yaml new file mode 100644 index 0000000..c0ef18a --- /dev/null +++ b/doi/helm/values.yaml @@ -0,0 +1,174 @@ +kubernetesClusterDomain: cluster.local + +replicaCount: 1 + +image: + repository: bucket.canfar.net/doi + pullPolicy: IfNotPresent + tag: "1.2.0" + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +podSecurityContext: {} + +securityContext: {} + +deployment: + hostname: example.org + doi: + # Optional rename of the application from the default "doi". + applicationName: "doi" + + # The endpoint to serve this from. If applicationName is changed, this should match. + endpoint: "/doi" + + # VOSI endpoint used by Kubernetes health probes and the Helm test. + availabilityPath: "/doi/availability" + + # Set the Registry URL pointing to the desired registry (https:// URL). + # registryURL: https://example.org/reg + + # ID (URI) of the GMS Service. Required for auth-related registry lookups. + # gmsID: ivo://example.org/gms + + # URI or URL of the OIDC (IAM) server. Used to validate incoming tokens. + # oidcURI: https://example.org/oidc + + # The IdentityManager class handling authentication. + identityManagerClass: org.opencadc.auth.StandardIdentityManager + + config: + # VOSpace URI to the parent DOI folder. + vospaceParentUri: "vos://example.org~arc/doi" + + # Prefix prepended to the DOI name for metadata stored in VOSpace. + metaDataPrefix: "doi" + + # Prefix prepended to the DOI name to create the backing GMS group name. + groupPrefix: "doi" + + # DOI landing page URL. + landingUrl: "https://example.org/doi" + + # DataCite MDS REST endpoint. + mdsUrl: "https://mds.datacite.org" + + # Registered prefix for the DataCite account. + accountPrefix: "" + + # Optional DOI identifier prefix. + doiIdentifierPrefix: "" + + # Optional publisher group URI for alternative DOI settings. + publisherGroupURI: "" + + # Optional self-publish flag for alternative DOI settings. + selfPublish: "" + + # Developer testing only. + randomTestID: false + + datacite: + auth: + # Existing Secret containing DataCite credentials. + existingSecret: "doi-datacite" + secretKeys: + username: username + password: password + + certificates: + # Existing Secret containing doiadmin.pem and cadcproxy.pem. + existingSecret: "doi-certs" + secretKeys: + doiadmin: doiadmin.pem + cadcproxy: cadcproxy.pem + + # Optionally set the DEBUG port. + # extraEnv: + # - name: CATALINA_OPTS + # value: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5555" + # extraPorts: + # - containerPort: 5555 + # protocol: TCP + + # This applies to the DOI service pod. + # nodeAffinity: {} + + # Optionally mount additional config or certificates. + # extraVolumeMounts: + # - mountPath: "/config/cacerts" + # name: cacert-volume + # extraVolumes: + # - name: cacert-volume + # secret: + # defaultMode: 420 + # secretName: doi-cacert-secret + + # Groups that can alter logging. Empty array means nobody can alter it. + loggingGroups: [] + + resources: + requests: + memory: "500Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "750m" + + # Specify extra hostnames that will be added to the Pod's /etc/hosts file. + # extraHosts: + # - ip: 127.3.34.5 + # hostname: myhost.example.org + # extraHosts: [] + +tolerations: [] + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: false + className: "" + hosts: + - host: chart-example.local + paths: + - path: /doi + pathType: Prefix + tls: [] + +volumeInit: + image: busybox:1.36 + imagePullPolicy: IfNotPresent + securityContext: {} + podSecurityContext: {} + +serviceAccount: + create: false + automount: false + annotations: {} + name: "" + +livenessProbe: + httpGet: + path: /doi/availability + port: http + periodSeconds: 30 + +readinessProbe: + httpGet: + path: /doi/availability + port: http + periodSeconds: 15 + +startupProbe: + httpGet: + path: /doi/availability + port: http + initialDelaySeconds: 10 + timeoutSeconds: 30 + periodSeconds: 10 + failureThreshold: 30 From 0629c4bd35d3ebc3a986a6a58cd40f5e067f2759 Mon Sep 17 00:00:00 2001 From: Wenbin Li Date: Tue, 28 Jul 2026 17:08:21 -0700 Subject: [PATCH 2/3] fix: restructure DOI Helm values --- doi/README.md | 4 +- doi/helm/README.md | 25 ++- doi/helm/config/cadc-log.properties | 2 +- doi/helm/config/cadc-registry.properties | 6 +- doi/helm/config/catalina.properties | 4 +- doi/helm/config/doi.properties | 20 +- doi/helm/config/war-rename.conf | 4 +- doi/helm/examples/values.example.yaml | 53 ++++-- doi/helm/templates/_helpers.tpl | 24 ++- doi/helm/templates/deployment.yaml | 24 +-- doi/helm/templates/httproute.yaml | 39 ++++ doi/helm/templates/tests/test-connection.yaml | 2 +- doi/helm/values.yaml | 180 ++++++++++-------- 13 files changed, 235 insertions(+), 152 deletions(-) create mode 100644 doi/helm/templates/httproute.yaml diff --git a/doi/README.md b/doi/README.md index 4171faf..d965a47 100644 --- a/doi/README.md +++ b/doi/README.md @@ -125,10 +125,10 @@ Validate the chart before installation: ``` helm lint doi/helm \ - --set deployment.doi.config.accountPrefix=10.5072 + --set application.config.accountPrefix=10.5072 helm template doi doi/helm \ --namespace doi \ - --set deployment.doi.config.accountPrefix=10.5072 + --set application.config.accountPrefix=10.5072 ``` Install it with environment-specific values: diff --git a/doi/helm/README.md b/doi/helm/README.md index ffe482d..c3e9dc4 100644 --- a/doi/helm/README.md +++ b/doi/helm/README.md @@ -25,13 +25,12 @@ kubectl create secret generic doi-certs \ Set: ```yaml -deployment: - doi: - datacite: - auth: - existingSecret: doi-datacite - certificates: - existingSecret: doi-certs +application: + datacite: + auth: + existingSecret: doi-datacite + certificates: + existingSecret: doi-certs ``` ## Example Values @@ -44,15 +43,15 @@ Render and lint the chart with non-secret placeholder Secret names: ```shell helm lint doi/helm \ - --set deployment.doi.datacite.auth.existingSecret=doi-datacite \ - --set deployment.doi.certificates.existingSecret=doi-certs \ - --set deployment.doi.config.accountPrefix=10.5072 + --set application.datacite.auth.existingSecret=doi-datacite \ + --set application.certificates.existingSecret=doi-certs \ + --set application.config.accountPrefix=10.5072 helm template doi doi/helm \ --namespace doi \ - --set deployment.doi.datacite.auth.existingSecret=doi-datacite \ - --set deployment.doi.certificates.existingSecret=doi-certs \ - --set deployment.doi.config.accountPrefix=10.5072 + --set application.datacite.auth.existingSecret=doi-datacite \ + --set application.certificates.existingSecret=doi-certs \ + --set application.config.accountPrefix=10.5072 ``` Dry-run against a cluster: diff --git a/doi/helm/config/cadc-log.properties b/doi/helm/config/cadc-log.properties index f02879e..8432ebc 100644 --- a/doi/helm/config/cadc-log.properties +++ b/doi/helm/config/cadc-log.properties @@ -1,3 +1,3 @@ -{{- range $val := .Values.deployment.doi.loggingGroups }} +{{- range $val := .Values.application.loggingGroups }} group = {{ $val }} {{- end }} diff --git a/doi/helm/config/cadc-registry.properties b/doi/helm/config/cadc-registry.properties index a14245f..378fc6e 100644 --- a/doi/helm/config/cadc-registry.properties +++ b/doi/helm/config/cadc-registry.properties @@ -1,7 +1,7 @@ # # local authority map # -{{- with .Values.deployment.doi.gmsID }} +{{- with .Values.application.gmsID }} ivo://ivoa.net/std/GMS#search-1.0 = {{ . }} ivo://ivoa.net/std/GMS#search-0.1 = {{ . }} ivo://ivoa.net/std/GMS#users-1.0 = {{ . }} @@ -9,12 +9,12 @@ ivo://ivoa.net/std/UMS#users-0.1 = {{ . }} ivo://ivoa.net/std/UMS#users-1.0 = {{ . }} ivo://ivoa.net/sso#tls-with-password = {{ . }} {{- end }} -{{- with .Values.deployment.doi.oidcURI }} +{{- with .Values.application.oidcURI }} ivo://ivoa.net/sso#OAuth = {{ . }} ivo://ivoa.net/sso#OpenID = {{ . }} {{- end }} -{{- $raw := .Values.deployment.doi.registryURL -}} +{{- $raw := .Values.application.registryURL -}} {{- $urls := ternary (list $raw) $raw (kindIs "string" $raw) -}} {{- range $urls }} ca.nrc.cadc.reg.client.RegistryClient.baseURL = {{ . }} diff --git a/doi/helm/config/catalina.properties b/doi/helm/config/catalina.properties index 58a6a0a..b949552 100644 --- a/doi/helm/config/catalina.properties +++ b/doi/helm/config/catalina.properties @@ -2,9 +2,9 @@ tomcat.connector.connectionTimeout=180000 tomcat.connector.keepAliveTimeout=180000 tomcat.connector.secure=true tomcat.connector.scheme=https -tomcat.connector.proxyName={{ .Values.deployment.hostname }} +tomcat.connector.proxyName={{ include "doi.hostname" . }} tomcat.connector.proxyPort=443 ca.nrc.cadc.auth.PrincipalExtractor.enableClientCertHeader=true ca.nrc.cadc.util.Log4jInit.messageOnly=true # (default: ca.nrc.cadc.auth.NoOpIdentityManager) -ca.nrc.cadc.auth.IdentityManager={{ .Values.deployment.doi.identityManagerClass }} +ca.nrc.cadc.auth.IdentityManager={{ .Values.application.identityManagerClass }} diff --git a/doi/helm/config/doi.properties b/doi/helm/config/doi.properties index a9e7abb..9e80fa8 100644 --- a/doi/helm/config/doi.properties +++ b/doi/helm/config/doi.properties @@ -1,19 +1,19 @@ -ca.nrc.cadc.doi.vospaceParentUri = {{ .Values.deployment.doi.config.vospaceParentUri | required ".Values.deployment.doi.config.vospaceParentUri is required" }} -ca.nrc.cadc.doi.metaDataPrefix = {{ .Values.deployment.doi.config.metaDataPrefix | required ".Values.deployment.doi.config.metaDataPrefix is required" }} -ca.nrc.cadc.doi.groupPrefix = {{ .Values.deployment.doi.config.groupPrefix | required ".Values.deployment.doi.config.groupPrefix is required" }} -ca.nrc.cadc.doi.landingUrl = {{ .Values.deployment.doi.config.landingUrl | required ".Values.deployment.doi.config.landingUrl is required" }} -ca.nrc.cadc.doi.datacite.mdsUrl = {{ .Values.deployment.doi.config.mdsUrl | required ".Values.deployment.doi.config.mdsUrl is required" }} -ca.nrc.cadc.doi.datacite.accountPrefix = {{ .Values.deployment.doi.config.accountPrefix | required ".Values.deployment.doi.config.accountPrefix is required" }} -{{- with .Values.deployment.doi.config.doiIdentifierPrefix }} +ca.nrc.cadc.doi.vospaceParentUri = {{ .Values.application.config.vospaceParentUri | required ".Values.application.config.vospaceParentUri is required" }} +ca.nrc.cadc.doi.metaDataPrefix = {{ .Values.application.config.metaDataPrefix | required ".Values.application.config.metaDataPrefix is required" }} +ca.nrc.cadc.doi.groupPrefix = {{ .Values.application.config.groupPrefix | required ".Values.application.config.groupPrefix is required" }} +ca.nrc.cadc.doi.landingUrl = {{ .Values.application.config.landingUrl | required ".Values.application.config.landingUrl is required" }} +ca.nrc.cadc.doi.datacite.mdsUrl = {{ .Values.application.config.mdsUrl | required ".Values.application.config.mdsUrl is required" }} +ca.nrc.cadc.doi.datacite.accountPrefix = {{ .Values.application.config.accountPrefix | required ".Values.application.config.accountPrefix is required" }} +{{- with .Values.application.config.doiIdentifierPrefix }} ca.nrc.cadc.doi.doiIdentifierPrefix = {{ . }} {{- end }} -{{- with .Values.deployment.doi.config.publisherGroupURI }} +{{- with .Values.application.config.publisherGroupURI }} ca.nrc.cadc.doi.publisherGroupURI = {{ . }} {{- end }} -{{- with .Values.deployment.doi.config.selfPublish }} +{{- with .Values.application.config.selfPublish }} ca.nrc.cadc.doi.selfPublish = {{ . }} {{- end }} -{{- if .Values.deployment.doi.config.randomTestID }} +{{- if .Values.application.config.randomTestID }} ca.nrc.cadc.doi.randomTestID = true {{- end }} # ca.nrc.cadc.doi.datacite.username and ca.nrc.cadc.doi.datacite.password are appended at pod startup from Secret {{ include "doi.dataciteAuthSecretName" . }}. diff --git a/doi/helm/config/war-rename.conf b/doi/helm/config/war-rename.conf index 1cfbd1d..ce2fe27 100644 --- a/doi/helm/config/war-rename.conf +++ b/doi/helm/config/war-rename.conf @@ -1,3 +1,3 @@ -{{- if and .Values.deployment.doi.applicationName (ne .Values.deployment.doi.applicationName "doi") }} -mv doi.war {{ .Values.deployment.doi.applicationName }}.war +{{- if and .Values.application.applicationName (ne .Values.application.applicationName "doi") }} +mv doi.war {{ .Values.application.applicationName }}.war {{- end }} diff --git a/doi/helm/examples/values.example.yaml b/doi/helm/examples/values.example.yaml index eff3354..c7c7490 100644 --- a/doi/helm/examples/values.example.yaml +++ b/doi/helm/examples/values.example.yaml @@ -10,27 +10,25 @@ image: imagePullSecrets: - name: bucket-registry-auth -deployment: - hostname: doi.example.org - doi: - registryURL: https://doi.example.org/reg - gmsID: ivo://example.org/gms - oidcURI: https://iam.example.org/ - - config: - vospaceParentUri: vos://example.org~arc/doi - metaDataPrefix: doi - groupPrefix: doi - landingUrl: https://doi.example.org/doi - mdsUrl: https://mds.datacite.org - accountPrefix: "10.5072" - - datacite: - auth: - existingSecret: doi-datacite - - certificates: - existingSecret: doi-certs +application: + registryURL: https://doi.example.org/reg + gmsID: ivo://example.org/gms + oidcURI: https://iam.example.org/ + + config: + vospaceParentUri: vos://example.org~arc/doi + metaDataPrefix: doi + groupPrefix: doi + landingUrl: https://doi.example.org/doi + mdsUrl: https://mds.datacite.org + accountPrefix: "10.5072" + + datacite: + auth: + existingSecret: doi-datacite + + certificates: + existingSecret: doi-certs ingress: enabled: true @@ -40,3 +38,16 @@ ingress: paths: - path: /doi pathType: Prefix + +httpRoute: + enabled: false + parentRefs: + - name: gateway + sectionName: http + hostnames: + - doi.example.org + rules: + - matches: + - path: + type: PathPrefix + value: /doi diff --git a/doi/helm/templates/_helpers.tpl b/doi/helm/templates/_helpers.tpl index 7808780..6583829 100644 --- a/doi/helm/templates/_helpers.tpl +++ b/doi/helm/templates/_helpers.tpl @@ -32,6 +32,26 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} +{{/* +Use the Gateway API hostname when HTTPRoute is enabled; otherwise use the +first configured Ingress hostname. DOI needs this to construct external URLs. +*/}} +{{- define "doi.hostname" -}} +{{- if .Values.httpRoute.enabled -}} +{{- $hostnames := .Values.httpRoute.hostnames | default (list) -}} +{{- if eq (len $hostnames) 0 -}} +{{- fail "httpRoute.hostnames must contain at least one hostname when httpRoute.enabled is true" -}} +{{- end -}} +{{- index $hostnames 0 -}} +{{- else -}} +{{- $hosts := .Values.ingress.hosts | default (list) -}} +{{- if eq (len $hosts) 0 -}} +{{- fail "ingress.hosts must contain at least one hostname when httpRoute is disabled" -}} +{{- end -}} +{{- index (index $hosts 0) "host" -}} +{{- end -}} +{{- end }} + {{/* Create the name of the service account to use. */}} @@ -47,12 +67,12 @@ Create the name of the service account to use. DataCite credential Secret name. */}} {{- define "doi.dataciteAuthSecretName" -}} -{{- required "deployment.doi.datacite.auth.existingSecret is required" .Values.deployment.doi.datacite.auth.existingSecret -}} +{{- required "application.datacite.auth.existingSecret is required" .Values.application.datacite.auth.existingSecret -}} {{- end -}} {{/* PEM certificate Secret name. */}} {{- define "doi.certificateSecretName" -}} -{{- required "deployment.doi.certificates.existingSecret is required" .Values.deployment.doi.certificates.existingSecret -}} +{{- required "application.certificates.existingSecret is required" .Values.application.certificates.existingSecret -}} {{- end -}} diff --git a/doi/helm/templates/deployment.yaml b/doi/helm/templates/deployment.yaml index ddc17ab..4c849f4 100644 --- a/doi/helm/templates/deployment.yaml +++ b/doi/helm/templates/deployment.yaml @@ -1,9 +1,9 @@ {{- $containerPort := .Values.service.port -}} -{{- $auth := .Values.deployment.doi.datacite.auth | default dict -}} +{{- $auth := .Values.application.datacite.auth | default dict -}} {{- $authKeys := $auth.secretKeys | default dict -}} {{- $usernameKey := $authKeys.username | default "username" -}} {{- $passwordKey := $authKeys.password | default "password" -}} -{{- $certs := .Values.deployment.doi.certificates | default dict -}} +{{- $certs := .Values.application.certificates | default dict -}} {{- $certKeys := $certs.secretKeys | default dict -}} {{- $doiadminKey := $certKeys.doiadmin | default "doiadmin.pem" -}} {{- $cadcproxyKey := $certKeys.cadcproxy | default "cadcproxy.pem" -}} @@ -32,7 +32,7 @@ spec: {{- end }} serviceAccountName: {{ include "doi.serviceAccountName" . }} automountServiceAccountToken: {{ .Values.serviceAccount.automount }} - {{- with .Values.deployment.doi.nodeAffinity }} + {{- with .Values.application.nodeAffinity }} affinity: nodeAffinity: {{- toYaml . | nindent 10 }} @@ -88,15 +88,15 @@ spec: name: {{ .Release.Name }}-tomcat resources: requests: - memory: {{ .Values.deployment.doi.resources.requests.memory }} - cpu: {{ .Values.deployment.doi.resources.requests.cpu }} + memory: {{ .Values.application.resources.requests.memory }} + cpu: {{ .Values.application.resources.requests.cpu }} limits: - memory: {{ .Values.deployment.doi.resources.limits.memory }} - cpu: {{ .Values.deployment.doi.resources.limits.cpu }} + memory: {{ .Values.application.resources.limits.memory }} + cpu: {{ .Values.application.resources.limits.cpu }} securityContext: {{- $tomcatSC := merge (dict "allowPrivilegeEscalation" false) (.Values.securityContext | default dict) }} {{- toYaml $tomcatSC | nindent 12 }} - {{- with .Values.deployment.doi.extraEnv }} + {{- with .Values.application.extraEnv }} env: {{- toYaml . | nindent 12 }} {{- end }} @@ -104,13 +104,13 @@ spec: - name: http containerPort: {{ $containerPort }} protocol: TCP - {{- with .Values.deployment.doi.extraPorts }} + {{- with .Values.application.extraPorts }} {{- toYaml . | nindent 12 }} {{- end }} volumeMounts: - mountPath: "/config" name: config-merged - {{- with .Values.deployment.doi.extraVolumeMounts }} + {{- with .Values.application.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} {{- with .Values.livenessProbe }} @@ -125,7 +125,7 @@ spec: startupProbe: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.deployment.extraHosts }} + {{- with .Values.application.extraHosts }} hostAliases: {{- range $extraHost := . }} - ip: {{ $extraHost.ip }} @@ -149,6 +149,6 @@ spec: secretName: {{ include "doi.certificateSecretName" . }} - name: config-merged emptyDir: {} - {{- with .Values.deployment.doi.extraVolumes }} + {{- with .Values.application.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/doi/helm/templates/httproute.yaml b/doi/helm/templates/httproute.yaml new file mode 100644 index 0000000..0bc79dd --- /dev/null +++ b/doi/helm/templates/httproute.yaml @@ -0,0 +1,39 @@ +{{- if .Values.httpRoute.enabled -}} +{{- $serviceName := printf "%s-tomcat-svc" .Release.Name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ .Release.Name }}-httproute + namespace: {{ .Release.Namespace }} + labels: + {{- include "doi.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $serviceName }} + port: {{ $servicePort }} + weight: 1 + {{- end }} +{{- end }} diff --git a/doi/helm/templates/tests/test-connection.yaml b/doi/helm/templates/tests/test-connection.yaml index 7ff4100..71a9e22 100644 --- a/doi/helm/templates/tests/test-connection.yaml +++ b/doi/helm/templates/tests/test-connection.yaml @@ -12,5 +12,5 @@ spec: - name: wget image: busybox:1.36 command: ['wget'] - args: ['{{ .Release.Name }}-tomcat-svc:{{ .Values.service.port }}{{ .Values.deployment.doi.availabilityPath }}'] + args: ['{{ .Release.Name }}-tomcat-svc:{{ .Values.service.port }}{{ .Values.application.availabilityPath }}'] restartPolicy: Never diff --git a/doi/helm/values.yaml b/doi/helm/values.yaml index c0ef18a..489303e 100644 --- a/doi/helm/values.yaml +++ b/doi/helm/values.yaml @@ -16,107 +16,105 @@ podSecurityContext: {} securityContext: {} -deployment: - hostname: example.org - doi: - # Optional rename of the application from the default "doi". - applicationName: "doi" +application: + # Optional rename of the application from the default "doi". + applicationName: "doi" - # The endpoint to serve this from. If applicationName is changed, this should match. - endpoint: "/doi" + # The endpoint to serve this from. If applicationName is changed, this should match. + endpoint: "/doi" - # VOSI endpoint used by Kubernetes health probes and the Helm test. - availabilityPath: "/doi/availability" + # VOSI endpoint used by Kubernetes health probes and the Helm test. + availabilityPath: "/doi/availability" - # Set the Registry URL pointing to the desired registry (https:// URL). - # registryURL: https://example.org/reg + # Set the Registry URL pointing to the desired registry (https:// URL). + # registryURL: https://example.org/reg - # ID (URI) of the GMS Service. Required for auth-related registry lookups. - # gmsID: ivo://example.org/gms + # ID (URI) of the GMS Service. Required for auth-related registry lookups. + # gmsID: ivo://example.org/gms - # URI or URL of the OIDC (IAM) server. Used to validate incoming tokens. - # oidcURI: https://example.org/oidc + # URI or URL of the OIDC (IAM) server. Used to validate incoming tokens. + # oidcURI: https://example.org/oidc - # The IdentityManager class handling authentication. - identityManagerClass: org.opencadc.auth.StandardIdentityManager + # The IdentityManager class handling authentication. + identityManagerClass: org.opencadc.auth.StandardIdentityManager - config: - # VOSpace URI to the parent DOI folder. - vospaceParentUri: "vos://example.org~arc/doi" + config: + # VOSpace URI to the parent DOI folder. + vospaceParentUri: "vos://example.org~arc/doi" - # Prefix prepended to the DOI name for metadata stored in VOSpace. - metaDataPrefix: "doi" + # Prefix prepended to the DOI name for metadata stored in VOSpace. + metaDataPrefix: "doi" - # Prefix prepended to the DOI name to create the backing GMS group name. - groupPrefix: "doi" + # Prefix prepended to the DOI name to create the backing GMS group name. + groupPrefix: "doi" - # DOI landing page URL. - landingUrl: "https://example.org/doi" + # DOI landing page URL. + landingUrl: "https://example.org/doi" - # DataCite MDS REST endpoint. - mdsUrl: "https://mds.datacite.org" + # DataCite MDS REST endpoint. + mdsUrl: "https://mds.datacite.org" - # Registered prefix for the DataCite account. - accountPrefix: "" + # Registered prefix for the DataCite account. + accountPrefix: "" - # Optional DOI identifier prefix. - doiIdentifierPrefix: "" + # Optional DOI identifier prefix. + doiIdentifierPrefix: "" - # Optional publisher group URI for alternative DOI settings. - publisherGroupURI: "" + # Optional publisher group URI for alternative DOI settings. + publisherGroupURI: "" - # Optional self-publish flag for alternative DOI settings. - selfPublish: "" + # Optional self-publish flag for alternative DOI settings. + selfPublish: "" - # Developer testing only. - randomTestID: false + # Developer testing only. + randomTestID: false - datacite: - auth: - # Existing Secret containing DataCite credentials. - existingSecret: "doi-datacite" - secretKeys: - username: username - password: password - - certificates: - # Existing Secret containing doiadmin.pem and cadcproxy.pem. - existingSecret: "doi-certs" + datacite: + auth: + # Existing Secret containing DataCite credentials. + existingSecret: "doi-datacite" secretKeys: - doiadmin: doiadmin.pem - cadcproxy: cadcproxy.pem - - # Optionally set the DEBUG port. - # extraEnv: - # - name: CATALINA_OPTS - # value: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5555" - # extraPorts: - # - containerPort: 5555 - # protocol: TCP - - # This applies to the DOI service pod. - # nodeAffinity: {} - - # Optionally mount additional config or certificates. - # extraVolumeMounts: - # - mountPath: "/config/cacerts" - # name: cacert-volume - # extraVolumes: - # - name: cacert-volume - # secret: - # defaultMode: 420 - # secretName: doi-cacert-secret - - # Groups that can alter logging. Empty array means nobody can alter it. - loggingGroups: [] - - resources: - requests: - memory: "500Mi" - cpu: "500m" - limits: - memory: "1Gi" - cpu: "750m" + username: username + password: password + + certificates: + # Existing Secret containing doiadmin.pem and cadcproxy.pem. + existingSecret: "doi-certs" + secretKeys: + doiadmin: doiadmin.pem + cadcproxy: cadcproxy.pem + + # Optionally set the DEBUG port. + # extraEnv: + # - name: CATALINA_OPTS + # value: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5555" + # extraPorts: + # - containerPort: 5555 + # protocol: TCP + + # This applies to the DOI service pod. + # nodeAffinity: {} + + # Optionally mount additional config or certificates. + # extraVolumeMounts: + # - mountPath: "/config/cacerts" + # name: cacert-volume + # extraVolumes: + # - name: cacert-volume + # secret: + # defaultMode: 420 + # secretName: doi-cacert-secret + + # Groups that can alter logging. Empty array means nobody can alter it. + loggingGroups: [] + + resources: + requests: + memory: "500Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "750m" # Specify extra hostnames that will be added to the Pod's /etc/hosts file. # extraHosts: @@ -140,6 +138,22 @@ ingress: pathType: Prefix tls: [] +# Expose the service with a Kubernetes Gateway API HTTPRoute. +httpRoute: + enabled: false + annotations: {} + parentRefs: + - name: gateway + sectionName: http + # namespace: default + hostnames: + - chart-example.local + rules: + - matches: + - path: + type: PathPrefix + value: /doi + volumeInit: image: busybox:1.36 imagePullPolicy: IfNotPresent From 5c2b3b7e04a47e6d4884c0b91f034c14047f9c7e Mon Sep 17 00:00:00 2001 From: Wenbin Li Date: Wed, 29 Jul 2026 09:34:42 -0700 Subject: [PATCH 3/3] fix: support cookie signature public key --- doi/helm/README.md | 13 ++++++++++++- doi/helm/examples/values.example.yaml | 5 +++++ doi/helm/templates/deployment.yaml | 19 +++++++++++++++++++ doi/helm/values.yaml | 9 +++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/doi/helm/README.md b/doi/helm/README.md index c3e9dc4..6f678f8 100644 --- a/doi/helm/README.md +++ b/doi/helm/README.md @@ -2,7 +2,7 @@ Digital Object Identifier service Helm chart. -This chart deploys the CADC DOI Tomcat service. Non-secret configuration is rendered into a ConfigMap under `/config`; DataCite credentials and the required `doiadmin.pem` and `cadcproxy.pem` files are read from Kubernetes Secrets and merged into the runtime config by an init container. +This chart deploys the CADC DOI Tomcat service. Non-secret configuration is rendered into a ConfigMap under `/config`; DataCite credentials and the required `doiadmin.pem` and `cadcproxy.pem` files are read from Kubernetes Secrets and merged into the runtime config by an init container. An optional `RsaSignaturePub.key` can also be projected from a Secret for browser cookie validation. ## Required Secrets @@ -22,6 +22,13 @@ kubectl create secret generic doi-certs \ --from-file=cadcproxy.pem=./cadcproxy.pem ``` +Create the optional Secret containing the cookie-signature public key: + +```shell +kubectl create secret generic doi-cookie-signature-public-key \ + --from-file=RsaSignaturePub.key=./RsaSignaturePub.key +``` + Set: ```yaml @@ -31,6 +38,10 @@ application: existingSecret: doi-datacite certificates: existingSecret: doi-certs + cookieSignaturePublicKey: + existingSecret: + name: doi-cookie-signature-public-key + path: RsaSignaturePub.key ``` ## Example Values diff --git a/doi/helm/examples/values.example.yaml b/doi/helm/examples/values.example.yaml index c7c7490..4deb5e5 100644 --- a/doi/helm/examples/values.example.yaml +++ b/doi/helm/examples/values.example.yaml @@ -30,6 +30,11 @@ application: certificates: existingSecret: doi-certs + cookieSignaturePublicKey: + existingSecret: + name: doi-cookie-signature-public-key + path: RsaSignaturePub.key + ingress: enabled: true className: traefik diff --git a/doi/helm/templates/deployment.yaml b/doi/helm/templates/deployment.yaml index 4c849f4..d420d6d 100644 --- a/doi/helm/templates/deployment.yaml +++ b/doi/helm/templates/deployment.yaml @@ -7,6 +7,13 @@ {{- $certKeys := $certs.secretKeys | default dict -}} {{- $doiadminKey := $certKeys.doiadmin | default "doiadmin.pem" -}} {{- $cadcproxyKey := $certKeys.cadcproxy | default "cadcproxy.pem" -}} +{{- $cookieKey := .Values.application.cookieSignaturePublicKey | default dict -}} +{{- $cookieKeySecret := $cookieKey.existingSecret | default dict -}} +{{- $cookieKeySecretName := trim (($cookieKeySecret.name | default "") | toString) -}} +{{- $cookieKeyPath := trim (($cookieKeySecret.path | default "RsaSignaturePub.key") | toString) -}} +{{- if empty $cookieKeyPath -}} +{{- $cookieKeyPath = "RsaSignaturePub.key" -}} +{{- end -}} apiVersion: apps/v1 kind: Deployment metadata: @@ -139,8 +146,20 @@ spec: {{- end }} volumes: - name: config-source + {{- if $cookieKeySecretName }} + projected: + sources: + - configMap: + name: {{ .Release.Name }}-config + - secret: + name: {{ $cookieKeySecretName }} + items: + - key: RsaSignaturePub.key + path: {{ $cookieKeyPath | quote }} + {{- else }} configMap: name: {{ .Release.Name }}-config + {{- end }} - name: datacite-secret secret: secretName: {{ include "doi.dataciteAuthSecretName" . }} diff --git a/doi/helm/values.yaml b/doi/helm/values.yaml index 489303e..4b0a835 100644 --- a/doi/helm/values.yaml +++ b/doi/helm/values.yaml @@ -84,6 +84,15 @@ application: doiadmin: doiadmin.pem cadcproxy: cadcproxy.pem + # Mount an RSA public key for browser cookie validation from a Secret. + # The Secret data key must be RsaSignaturePub.key. Leave the Secret name + # empty when cookie validation is not required. + cookieSignaturePublicKey: + existingSecret: + name: "" + # Filename under /config. Defaults to RsaSignaturePub.key when empty. + path: "" + # Optionally set the DEBUG port. # extraEnv: # - name: CATALINA_OPTS