From 410dd003f7743098bb8df949919fe048c5de75d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Guy=20S=C3=BC=C3=9F?= Date: Wed, 16 Jul 2025 10:01:45 +1000 Subject: [PATCH] Initial commit for chart --- charts/fhirflare-ig-toolkit/Chart.yaml | 19 ++ .../templates/_helpers.tpl | 152 +++++++++ .../templates/deployment.yaml | 37 +++ .../templates/service.yaml | 11 + .../templates/tests/test-endpoints.yaml | 73 +++++ charts/fhirflare-ig-toolkit/values.yaml | 302 ++++++++++++++++++ 6 files changed, 594 insertions(+) create mode 100644 charts/fhirflare-ig-toolkit/Chart.yaml create mode 100644 charts/fhirflare-ig-toolkit/templates/_helpers.tpl create mode 100644 charts/fhirflare-ig-toolkit/templates/deployment.yaml create mode 100644 charts/fhirflare-ig-toolkit/templates/service.yaml create mode 100644 charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml create mode 100644 charts/fhirflare-ig-toolkit/values.yaml diff --git a/charts/fhirflare-ig-toolkit/Chart.yaml b/charts/fhirflare-ig-toolkit/Chart.yaml new file mode 100644 index 0000000..e76d72f --- /dev/null +++ b/charts/fhirflare-ig-toolkit/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: fhirflare-ig-toolkit +version: 0.1.0 +description: Helm chart for deploying the fhirflare-ig-toolkit application +type: application +appVersion: "latest" +icon: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit/raw/main/static/FHIRFLARE.png +keywords: + - fhir + - healthcare + - ig-toolkit +home: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit +maintainers: + - name: FHIRFLARE Team + email: jgsuess@gmail.com +dependencies: + - name: hapi-fhir-jpaserver + version: 0.20.0 + repository: https://hapifhir.github.io/hapi-fhir-jpaserver-starter/ \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/_helpers.tpl b/charts/fhirflare-ig-toolkit/templates/_helpers.tpl new file mode 100644 index 0000000..954d1e9 --- /dev/null +++ b/charts/fhirflare-ig-toolkit/templates/_helpers.tpl @@ -0,0 +1,152 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "hapi-fhir-jpaserver.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "hapi-fhir-jpaserver.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "hapi-fhir-jpaserver.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "hapi-fhir-jpaserver.labels" -}} +helm.sh/chart: {{ include "hapi-fhir-jpaserver.chart" . }} +{{ include "hapi-fhir-jpaserver.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "hapi-fhir-jpaserver.selectorLabels" -}} +app.kubernetes.io/name: {{ include "hapi-fhir-jpaserver.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "hapi-fhir-jpaserver.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "hapi-fhir-jpaserver.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create a default fully qualified postgresql name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "hapi-fhir-jpaserver.postgresql.fullname" -}} +{{- $name := default "postgresql" .Values.postgresql.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Get the Postgresql credentials secret name. +*/}} +{{- define "hapi-fhir-jpaserver.postgresql.secretName" -}} +{{- if .Values.postgresql.enabled -}} + {{- if .Values.postgresql.auth.existingSecret -}} + {{- printf "%s" .Values.postgresql.auth.existingSecret -}} + {{- else -}} + {{- printf "%s" (include "hapi-fhir-jpaserver.postgresql.fullname" .) -}} + {{- end -}} +{{- else }} + {{- if .Values.externalDatabase.existingSecret -}} + {{- printf "%s" .Values.externalDatabase.existingSecret -}} + {{- else -}} + {{ printf "%s-%s" (include "hapi-fhir-jpaserver.fullname" .) "external-db" }} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Get the Postgresql credentials secret key. +*/}} +{{- define "hapi-fhir-jpaserver.postgresql.secretKey" -}} +{{- if .Values.postgresql.enabled -}} + {{- if .Values.postgresql.auth.username -}} + {{- printf "%s" .Values.postgresql.auth.secretKeys.userPasswordKey -}} + {{- else -}} + {{- printf "%s" .Values.postgresql.auth.secretKeys.adminPasswordKey -}} + {{- end -}} +{{- else }} + {{- if .Values.externalDatabase.existingSecret -}} + {{- printf "%s" .Values.externalDatabase.existingSecretKey -}} + {{- else -}} + {{- printf "postgres-password" -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Add environment variables to configure database values +*/}} +{{- define "hapi-fhir-jpaserver.database.host" -}} +{{- ternary (include "hapi-fhir-jpaserver.postgresql.fullname" .) .Values.externalDatabase.host .Values.postgresql.enabled -}} +{{- end -}} + +{{/* +Add environment variables to configure database values +*/}} +{{- define "hapi-fhir-jpaserver.database.user" -}} +{{- if .Values.postgresql.enabled -}} + {{- printf "%s" .Values.postgresql.auth.username | default "postgres" -}} +{{- else -}} + {{- printf "%s" .Values.externalDatabase.user -}} +{{- end -}} +{{- end -}} + +{{/* +Add environment variables to configure database values +*/}} +{{- define "hapi-fhir-jpaserver.database.name" -}} +{{- ternary .Values.postgresql.auth.database .Values.externalDatabase.database .Values.postgresql.enabled -}} +{{- end -}} + +{{/* +Add environment variables to configure database values +*/}} +{{- define "hapi-fhir-jpaserver.database.port" -}} +{{- ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled -}} +{{- end -}} + +{{/* +Create the JDBC URL from the host, port and database name. +*/}} +{{- define "hapi-fhir-jpaserver.database.jdbcUrl" -}} +{{- $host := (include "hapi-fhir-jpaserver.database.host" .) -}} +{{- $port := (include "hapi-fhir-jpaserver.database.port" .) -}} +{{- $name := (include "hapi-fhir-jpaserver.database.name" .) -}} +{{- $appName := .Release.Name -}} +{{ printf "jdbc:postgresql://%s:%d/%s?ApplicationName=%s" $host (int $port) $name $appName }} +{{- end -}} diff --git a/charts/fhirflare-ig-toolkit/templates/deployment.yaml b/charts/fhirflare-ig-toolkit/templates/deployment.yaml new file mode 100644 index 0000000..9543256 --- /dev/null +++ b/charts/fhirflare-ig-toolkit/templates/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fhirflare +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: fhirflare + strategy: + type: Recreate + template: + spec: + containers: + - args: + - supervisord + - -c + - /etc/supervisord.conf + env: + - name: APP_BASE_URL + value: http://localhost:5000 + - name: APP_MODE + value: lite + - name: FLASK_APP + value: app.py + - name: FLASK_ENV + value: development + - name: HAPI_FHIR_URL + value: http://localhost:8080/fhir + - name: NODE_PATH + value: /usr/lib/node_modules + image: ghcr.io/jgsuess/fhirflare-ig-toolkit:latest + name: fhirflare + ports: + - containerPort: 5000 + protocol: TCP + restartPolicy: Always \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/service.yaml b/charts/fhirflare-ig-toolkit/templates/service.yaml new file mode 100644 index 0000000..9bf5e3b --- /dev/null +++ b/charts/fhirflare-ig-toolkit/templates/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: fhirflare +spec: + ports: + - name: "5000" + port: 5000 + targetPort: 5000 + selector: + io.kompose.service: fhirflare diff --git a/charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml b/charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml new file mode 100644 index 0000000..94a43cb --- /dev/null +++ b/charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "hapi-fhir-jpaserver.fullname" . }}-test-endpoints" + labels: + {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} + {{ include "hapi-fhir-jpaserver.fullname" . }}-client: "true" + app.kubernetes.io/component: tests + annotations: + "helm.sh/hook": test +spec: + restartPolicy: Never + automountServiceAccountToken: {{ .Values.tests.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.tests.podSecurityContext | nindent 4 }} + containers: + - name: test-metadata-endpoint + image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" + command: ["curl", "--fail-with-body"] + args: ["http://{{ include "hapi-fhir-jpaserver.fullname" . }}:{{ .Values.service.port }}/fhir/metadata?_summary=true"] + {{- with .Values.restrictedContainerSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.tests.resources }} + resources: {{- toYaml .Values.tests.resources | nindent 10 }} + {{- else if ne .Values.tests.resourcesPreset "none" }} + resources: {{- include "common.resources.preset" (dict "type" .Values.tests.resourcesPreset) | nindent 10 }} + {{- end }} + livenessProbe: + exec: + command: ["true"] + readinessProbe: + exec: + command: ["true"] + - name: test-patient-endpoint + image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" + command: ["curl", "--fail-with-body"] + args: ["http://{{ include "hapi-fhir-jpaserver.fullname" . }}:{{ .Values.service.port }}/fhir/Patient?_count=1&_summary=true"] + {{- with .Values.restrictedContainerSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.tests.resources }} + resources: {{- toYaml .Values.tests.resources | nindent 10 }} + {{- else if ne .Values.tests.resourcesPreset "none" }} + resources: {{- include "common.resources.preset" (dict "type" .Values.tests.resourcesPreset) | nindent 10 }} + {{- end }} + livenessProbe: + exec: + command: ["true"] + readinessProbe: + exec: + command: ["true"] + - name: test-metrics-endpoint + image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" + command: ["curl", "--fail-with-body"] + args: ["http://{{ include "hapi-fhir-jpaserver.fullname" . }}:{{ .Values.metrics.service.port }}/actuator/prometheus"] + {{- with .Values.restrictedContainerSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.tests.resources }} + resources: {{- toYaml .Values.tests.resources | nindent 10 }} + {{- else if ne .Values.tests.resourcesPreset "none" }} + resources: {{- include "common.resources.preset" (dict "type" .Values.tests.resourcesPreset) | nindent 10 }} + {{- end }} + livenessProbe: + exec: + command: ["true"] + readinessProbe: + exec: + command: ["true"] diff --git a/charts/fhirflare-ig-toolkit/values.yaml b/charts/fhirflare-ig-toolkit/values.yaml new file mode 100644 index 0000000..422b693 --- /dev/null +++ b/charts/fhirflare-ig-toolkit/values.yaml @@ -0,0 +1,302 @@ +# -- number of replicas to deploy +replicaCount: 1 + +image: + # -- registry where the HAPI FHIR server image is hosted + registry: docker.io + # -- the path inside the repository + repository: hapiproject/hapi + # -- the image tag. As of v5.7.0, this is the `distroless` flavor by default, add `-tomcat` to use the Tomcat-based image. + tag: "v8.0.0-1@sha256:9fbac7b012b4be91ba481e7008f1353ede4598bc99a36f3902b8abf873e70ed8" + # -- image pullPolicy to use + pullPolicy: IfNotPresent + +# -- image pull secrets to use when pulling the image +imagePullSecrets: [] + +# -- override the chart name +nameOverride: "" + +# -- override the chart fullname +fullnameOverride: "" + +# -- annotations applied to the server deployment +deploymentAnnotations: {} + +# -- annotations applied to the server pod +podAnnotations: {} + +# -- pod security context +podSecurityContext: + fsGroupChangePolicy: OnRootMismatch + runAsNonRoot: true + runAsGroup: 65532 + runAsUser: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + privileged: false + seccompProfile: + type: RuntimeDefault + +# service to expose the server +service: + # -- service type + type: ClusterIP + # -- port where the server will be exposed at + port: 8080 + +ingress: + # -- whether to create an Ingress to expose the FHIR server HTTP endpoint + enabled: false + # -- provide any additional annotations which may be required. Evaluated as a template. + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: fhir-server.127.0.0.1.nip.io + pathType: ImplementationSpecific + paths: ["/"] + # -- ingress TLS config + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). +# This is ignored if `resources` is set (`resources` is recommended for production). +# More information: +resourcesPreset: "medium" + +# -- configure the FHIR server's resource requests and limits +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# -- node selector for the pod +nodeSelector: {} + +# -- pod tolerations +tolerations: [] + +# -- pod affinity +affinity: {} + +# -- pod topology spread configuration +# see: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/#api +topologySpreadConstraints: + [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: ScheduleAnyway + # labelSelector: + # matchLabels: + # app.kubernetes.io/instance: hapi-fhir-jpaserver + # app.kubernetes.io/name: hapi-fhir-jpaserver + +postgresql: + # -- enable an included PostgreSQL DB. + # see for details + # if set to `false`, the values under `externalDatabase` are used + enabled: true + auth: + # -- name for a custom database to create + database: "fhir" + # -- Name of existing secret to use for PostgreSQL credentials + # `auth.postgresPassword`, `auth.password`, and `auth.replicationPassword` will be ignored and picked up from this secret + # The secret must contain the keys `postgres-password` (which is the password for "postgres" admin user), + # `password` (which is the password for the custom user to create when `auth.username` is set), + # and `replication-password` (which is the password for replication user). + # The secret might also contains the key `ldap-password` if LDAP is enabled. `ldap.bind_password` will be ignored and + # picked from this secret in this case. + # The value is evaluated as a template. + existingSecret: "" + +# -- readiness probe +# @ignored +readinessProbe: + httpGet: + path: /readyz + port: http + failureThreshold: 5 + initialDelaySeconds: 30 + periodSeconds: 20 + successThreshold: 1 + timeoutSeconds: 20 + +# -- liveness probe +# @ignored +livenessProbe: + httpGet: + path: /livez + port: http + failureThreshold: 5 + initialDelaySeconds: 30 + periodSeconds: 20 + successThreshold: 1 + timeoutSeconds: 30 + +# -- startup probe +# @ignored +startupProbe: + httpGet: + path: /readyz + port: http + failureThreshold: 10 + initialDelaySeconds: 30 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + +externalDatabase: + # -- external database host used with `postgresql.enabled=false` + host: localhost + # -- database port number + port: 5432 + # -- username for the external database + user: fhir + # -- database password + password: "" + # -- name of an existing secret resource containing the DB password in the `existingSecretKey` key + existingSecret: "" + # -- name of the key inside the `existingSecret` + existingSecretKey: "postgresql-password" + # -- database name + database: fhir + +# -- extra environment variables to set on the server container +extraEnv: + [] + # - name: SPRING_FLYWAY_BASELINE_ON_MIGRATE + # value: "true" + +podDisruptionBudget: + # -- Enable PodDisruptionBudget for the server pods. + # uses policy/v1/PodDisruptionBudget thus requiring k8s 1.21+ + enabled: false + # -- minimum available instances + minAvailable: 1 + # -- maximum unavailable instances + maxUnavailable: "" + +serviceAccount: + # -- Specifies whether a service account should be created. + create: false + # -- Annotations to add to the service account + annotations: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + # -- Automatically mount a ServiceAccount's API credentials? + automount: true + +metrics: + serviceMonitor: + # -- if enabled, creates a ServiceMonitor instance for Prometheus Operator-based monitoring + enabled: false + # -- additional labels to apply to the ServiceMonitor object, e.g. `release: prometheus` + additionalLabels: {} + # namespace: monitoring + # interval: 30s + # scrapeTimeout: 10s + service: + port: 8081 + +# @ignore +restrictedContainerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + privileged: false + capabilities: + drop: + - ALL + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + seccompProfile: + type: RuntimeDefault + +# @ignored +curl: + image: + registry: docker.io + repository: curlimages/curl + tag: 8.12.1@sha256:94e9e444bcba979c2ea12e27ae39bee4cd10bc7041a472c4727a558e213744e6 + +tests: + # -- whether the service account token should be auto-mounted for the test pods + automountServiceAccountToken: false + # -- set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). + # This is ignored if `resources` is set (`resources` is recommended for production). + # More information: + resourcesPreset: "nano" + # -- configure the test pods resource requests and limits + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + # @ignored + podSecurityContext: + fsGroupChangePolicy: OnRootMismatch + runAsNonRoot: true + runAsGroup: 65532 + runAsUser: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + +initContainers: + # -- set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). + # This is ignored if `resources` is set (`resources` is recommended for production). + # More information: + resourcesPreset: "nano" + # -- configure the init containers pods resource requests and limits + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# -- additional Spring Boot application config. Mounted as a file and automatically loaded by the application. +extraConfig: + "" + # # For example: + # | + # hapi: + # fhir: + # implementationguides: + # gh_0_1_0: + # url: https://build.fhir.org/ig/hl7-eu/gravitate-health/package.tgz + # name: hl7.eu.fhir.gh + # version: 0.1.0 + +# -- Optionally specify extra list of additional volumes +extraVolumes: [] + +# -- Optionally specify extra list of additional volumeMounts +extraVolumeMounts: []