diff --git a/.prettierignore b/.prettierignore index ada4c118f..ef4c0fc93 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,6 @@ src/components # docs/docs is auto-generated docs/docs # Ignore any minimized files -**/*.min.* \ No newline at end of file +**/*.min.* +# Ignore helm chart; Prettier can't handle go templates correctly +helm/** diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 000000000..5a72c91b6 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,30 @@ +apiVersion: v2 +name: metacatui +description: | + Helm chart for Kubernetes Deployment of MetacatUI, a web interface for DataONE repositories + (https://github.com/NCEAS/metacatui) + +# OCI Annotations - see https://github.com/helm/helm/pull/11204 +sources: + - https://github.com/NCEAS/metacatui + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.6.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "2.30.0" diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 000000000..a1d3443b5 --- /dev/null +++ b/helm/README.md @@ -0,0 +1,118 @@ +# MetacatUI Helm chart + +This is a helm chart for deploying MetacatUI. + +--- + +## Deployment in a Kubernetes cluster: + +1. Modify values.yaml as appropriate +2. install the helm chart: + + ```shell + $ helm -n knb upgrade --install knbmcui ./helm + ``` + +There's no need to set up any persistent storage, unless you wish to add your own theme. The chart +ships with [a few pre-defined themes](https://github.com/NCEAS/metacatui/tree/main/src/js/themes), +which can be selected in values.yaml. + +--- + +## MetacatUI Configuration Files ('config.js') + +The k8s version of Metacatui requires two 'config.js' configuration files: + +1. the "root config" at the path `{root}/config/config.js`, and +2. the "theme config" in the theme directory itself (for example, see the knb `config.js` at: + https://github.com/NCEAS/metacatui/blob/main/src/js/themes/knb/config.js) + +The "root config" file must, at an absolute minimum, contain the name of the theme to be used; e.g: + +```javascript +MetacatUI.AppConfig = { + theme: "knb", +}; +``` + +...and metacatui will then load that theme and the corresponding "theme config". + +If any additional settings are defined in the "root config", Metacatui will use them to override +corresponding settings in the "theme config". + +By default, this chart creates a simple "root config", which will contain any values of the form: +`key: stringValue,` or `key: intValue,` that are provided in the `appConfig:` section of +[values.yaml](./values.yaml). + +If you need to provide more-complex overrides, set `appConfig.enabled: false`, and manually +create your own configMap named `-metacatui-config-js`, containing your custom +config.js: + +```shell +kubectl create configmap -metacatui-config-js \ + --from-file=config.js= +``` + +--- + +## Using a Custom Theme + +See [the MetacatUI +documentation](https://nceas.github.io/metacatui/install/configuration/index.html) for help with +creating custom themes. + +If you wish to deploy MetacatUI with your own custom theme, instead of using one of the themes that +are bundled with metacatui, you will need to provide the files for that theme (including its +"theme config" file) on shared filesystem, accessed via a manually-created Persistent Volume (PV) +mount and a Persistent Volume Claim (PVC). Example files for creating PVs and PVCs are provided +in the [admin](./admin) directory. + +Once you've got the chart deployed (see above), next steps are: + +1. Copy your theme files to a directory on a filesystem that is accessible from your Kubernetes + cluster +2. Create a Persistent Volume (PV) pointing to the correct directory on the filesystem +3. Create a PVC for the PV, and edit the `customTheme:` section in values.yaml +4. upgrade the helm chart + + ```shell + $ helm -n knb upgrade --install knbmcui ./helm + ``` + +--- + +## Development on Localhost + +(e.g. Rancher Desktop/Docker Desktop) + +1. Create a namespace for the deployment (e.g. `mcui`) +2. Create a PV that is mapped to a local `hostPath` directory containing the source code +3. Create a PVC for the PV +4. Modify values.yaml: + 1. Add the name of the PVC, so MetacatUI can find the files + 2. Set your hostname for the Ingress definition +5. install the helm chart: + + ```shell + $ helm -n mcui upgrade --install --debug mcui ./helm + Release "mcui" has been upgraded. Happy Helming! + NAME: mcui + LAST DEPLOYED: Wed Apr 17 19:45:58 2024 + NAMESPACE: mcui + STATUS: deployed + REVISION: 11 + ...etc + ``` + +You can now edit the MetacatUI source files, and changes will be immediately visible in your k8s +deployment. + +You will likely need to edit the `config.js` file to get a minimal setup working. Example contents +for `config/config.js`: + +```javascript +MetacatUI.AppConfig = { + root: "/", + baseUrl: "https://dev.nceas.ucsb.edu/knb/d1/mn", +}; +``` diff --git a/helm/admin/metacatui-customtheme-pv.yaml b/helm/admin/metacatui-customtheme-pv.yaml new file mode 100644 index 000000000..77abf14d2 --- /dev/null +++ b/helm/admin/metacatui-customtheme-pv.yaml @@ -0,0 +1,30 @@ +## EXAMPLE file for manually creating a Persistent Volume to store a metacatui custom theme. +## Needed only if 'customTheme.enabled:' is set to 'true' in values.yaml. +## EDIT this file to replace "$RELEASENAME", "$YOUR-CLUSTER-ID" and the "rootPath" +apiVersion: v1 +kind: PersistentVolume +metadata: + # See https://github.com/DataONEorg/k8s-cluster/blob/main/storage/storage.md#dataone-volume-naming-conventions + # cephs-{release}-{function}-{instance}, where {release} usually = {namespace} + name: &pv-name cephfs-$RELEASENAME-metacatui-customtheme +spec: + accessModes: + - ReadOnlyMany + capacity: + storage: 100Mi + csi: + driver: cephfs.csi.ceph.com + nodeStageSecretRef: + # node stage secret name + name: csi-cephfs-$RELEASENAME-secret + # node stage secret namespace where above secret is created + namespace: ceph-csi-cephfs + volumeAttributes: + clusterID: $YOUR-CLUSTER-ID + fsName: cephfs + rootPath: /volumes/YOUR-subvol-group/YOUR-subvol/YOUR-ID/repos/YOUR_REPO/metacatui + staticVolume: "true" + volumeHandle: *pv-name + persistentVolumeReclaimPolicy: Retain + storageClassName: csi-cephfs-sc + volumeMode: Filesystem diff --git a/helm/admin/metacatui-customtheme-pvc.yaml b/helm/admin/metacatui-customtheme-pvc.yaml new file mode 100644 index 000000000..67a3c6048 --- /dev/null +++ b/helm/admin/metacatui-customtheme-pvc.yaml @@ -0,0 +1,20 @@ +## EXAMPLE file for manually creating a Persistent Volume Claim to access the PV containing a +## metacatui custom theme. +## Needed only if 'customTheme.enabled:' is set to 'true' in values.yaml. +## EDIT this file to replace "$RELEASENAME" and "$NAMESPACE" +## +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: $RELEASENAME-metacatui-customtheme + ## NOTE: namespace must match the deployment namespace + namespace: $NAMESPACE +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi + storageClassName: csi-cephfs-sc + volumeMode: Filesystem + volumeName: cephfs-RELEASENAME-metacatui-customtheme diff --git a/helm/admin/metacatui-sourcecode-pv.yaml b/helm/admin/metacatui-sourcecode-pv.yaml new file mode 100644 index 000000000..140e47a57 --- /dev/null +++ b/helm/admin/metacatui-sourcecode-pv.yaml @@ -0,0 +1,18 @@ +## EXAMPLE file for manually creating a Persistent Volume to store the metacatui source code, +## typically for development purposes (so edits can be seen in realtime). +## Needed only if 'source.from:' is set to 'pvc' in values.yaml. +## +apiVersion: v1 +kind: PersistentVolume +metadata: + name: metacatui-pv +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + hostPath: + path: /your/host/path/here # e.g. /Users/jones/development/metacatui/src + type: "" + persistentVolumeReclaimPolicy: Retain + volumeMode: Filesystem diff --git a/helm/admin/metacatui-sourcecode-pvc.yaml b/helm/admin/metacatui-sourcecode-pvc.yaml new file mode 100644 index 000000000..d0e9fa491 --- /dev/null +++ b/helm/admin/metacatui-sourcecode-pvc.yaml @@ -0,0 +1,18 @@ +## EXAMPLE file for manually creating a Persistent Volume Claim to access the PV containing the +## metacatui source code (see metacatui-pv.yaml), typically for development purposes. +## Needed only if 'source.from:' is set to 'pvc' in values.yaml. +## +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: metacatui-pvc + namespace: mcui +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: "" + volumeMode: Filesystem + volumeName: metacatui-pv diff --git a/helm/config/config.js b/helm/config/config.js new file mode 100644 index 000000000..fff3cae0d --- /dev/null +++ b/helm/config/config.js @@ -0,0 +1,17 @@ +MetacatUI.AppConfig = { + {{- $ignoreList := list "enabled" "theme" "root" "baseUrl" "metacatContext" "d1CNBaseUrl" -}} + {{- range $key, $value := .Values.appConfig }} + {{- if not (has $key $ignoreList) }} + {{- if eq (typeOf $value) "string" }} + {{- $key | nindent 4 }}: {{ $value | quote }}, + {{- else }} + {{- $key | nindent 4 }}: {{ $value }}, + {{- end }} + {{- end }} + {{- end -}} + {{- include "metacatui.cn.url" . | nindent 4 }} + theme: {{ required "metacatUiThemeName_is_REQUIRED" .Values.global.metacatUiThemeName | quote }}, + root: {{ required "root_is_REQUIRED" .Values.global.metacatUiWebRoot | quote }}, + metacatContext: {{ required "metacatAppContext_is_REQUIRED" .Values.global.metacatAppContext | quote }}, + baseUrl: {{ required "metacatExternalBaseUrl_is_REQUIRED" .Values.global.metacatExternalBaseUrl | quote }} +} diff --git a/helm/templates/.ingress.yaml.swp b/helm/templates/.ingress.yaml.swp new file mode 100644 index 000000000..c3a32c5bf Binary files /dev/null and b/helm/templates/.ingress.yaml.swp differ diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt new file mode 100644 index 000000000..3ad88b143 --- /dev/null +++ b/helm/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "metacatui.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "metacatui.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "metacatui.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "metacatui.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 000000000..da142bcc9 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,113 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "metacatui.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 "metacatui.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 "metacatui.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "metacatui.labels" -}} +helm.sh/chart: {{ include "metacatui.chart" . }} +{{ include "metacatui.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "metacatui.selectorLabels" -}} +app.kubernetes.io/name: {{ include "metacatui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "metacatui.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "metacatui.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Populate the dataone cn url +*/}} +{{- define "metacatui.cn.url" -}} +{{- $d1ClientCnUrl := .Values.global.d1ClientCnUrl }} +{{- if $d1ClientCnUrl }} +{{- if not (hasSuffix "/" $d1ClientCnUrl) -}} + {{- $d1ClientCnUrl = print $d1ClientCnUrl "/" -}} +{{- end -}} +{{- $baseCnURL := regexFind "http.?://[^/]*/" $d1ClientCnUrl }} +{{- if not $baseCnURL -}} +d1CNBaseUrl: "ERROR_IN_URL__{{ $d1ClientCnUrl }}", +{{- else -}} +d1CNBaseUrl: "{{ $baseCnURL }}", +{{- end }} +{{- end }} +{{- end }} + +{{/* +Remove trailing slash from root, if it exists +*/}} +{{- define "metacatui.clean.root" -}} +{{- $cleanedRoot := regexReplaceAll "/$" .Values.global.metacatUiWebRoot "" -}} +{{- $cleanedRoot }} +{{- end }} + +{{/* +generate file path for the web root mount +*/}} +{{- define "metacatui.root.mountpath" -}} +/usr/share/nginx/html{{ include "metacatui.clean.root" . }} +{{- end }} + +{{/* +validate and clean up '.Values.source.from' +*/}} +{{- define "metacatui.source.from" -}} +{{- $source := "" }} +{{- $defaultSrc := "package" }} +{{- if not (and .Values.source .Values.source.from) }} +{{- $source = $defaultSrc }} +{{- else }} +{{- $source = .Values.source.from }} +{{- end }} +{{- $allowedSourceVals := list "package" "git" "pvc" }} +{{- if not (has $source $allowedSourceVals) }} +{{- $source = $defaultSrc }} +{{- end }} +{{- $source }} +{{- end }} diff --git a/helm/templates/configmap.yaml b/helm/templates/configmap.yaml new file mode 100644 index 000000000..1ec251b47 --- /dev/null +++ b/helm/templates/configmap.yaml @@ -0,0 +1,11 @@ +{{- if .Values.appConfig.enabled }} +# Load all files in the "config" directory into a ConfigMap +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-metacatui-config-js + labels: + {{- include "metacatui.labels" . | nindent 4 }} +data: + {{- (tpl (.Files.Glob "config/config.js").AsConfig . ) | nindent 2 }} +{{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml new file mode 100644 index 000000000..69b4e0a2f --- /dev/null +++ b/helm/templates/deployment.yaml @@ -0,0 +1,167 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "metacatui.fullname" . }} + labels: + {{- include "metacatui.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "metacatui.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "metacatui.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "metacatui.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- $source := (include "metacatui.source.from" .) }} + {{- if ne $source "pvc" }} + initContainers: + - name: get-source + {{- if eq $source "git" }} + image: alpine/git:latest + {{- else }} + image: busybox:latest + {{- end }} + command: + - sh + - -c + - > + start=$(date +%s); + echo "Starting at $start"; + DEST="/metacatui{{ include "metacatui.clean.root" . }}"; + mkdir -p $DEST; + {{- if eq $source "git" }} + {{- $repoUrl := "" }} + {{- $revision := "" }} + {{- if and .Values.source .Values.source.git }} + {{- $repoUrl = .Values.source.git.repoUrl }} + {{- $revision = .Values.source.git.revision }} + {{- end }} + REPO='{{ $repoUrl | default "https://github.com/NCEAS/metacatui.git" }}'; + REV='{{ $revision | default .Chart.AppVersion }}'; + git clone -b $REV --depth 1 $REPO /tmp/metacatui/; + mv /tmp/metacatui/src/* $DEST; + finish=$(date +%s); + echo "git clone -b {{ $revision }} --depth 1 took $((finish - start)) sec"; + {{- else }} + {{- $version := "" }} + {{- $location := "" }} + {{- if and .Values.source .Values.source.package }} + {{- $version = .Values.source.package.version }} + {{- $location = .Values.source.package.location }} + {{- end }} + VERSION={{ $version | default .Chart.AppVersion }}; + FILENAME=$VERSION.zip; + LOC='{{ $location | default "https://github.com/NCEAS/metacatui/archive" }}'; + wget -O ./$FILENAME $LOC/$FILENAME; + unzip $FILENAME -d /tmp/; + mv /tmp/metacatui-$VERSION/src/* $DEST; + finish=$(date +%s); + echo "$FILENAME download and install took $((finish - start)) sec"; + {{- end }} + volumeMounts: + - name: {{ .Release.Name }}-mcui-source-files + mountPath: /metacatui + {{- end }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository | default "nginx" }}: + {{- .Values.image.tag | default "latest" }}" + imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if .Values.livenessProbeEnabled }} + livenessProbe: + {{- if .Values.livenessProbe }} + {{- with .Values.livenessProbe }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- else }} + httpGet: + path: {{ .Values.global.metacatUiWebRoot }} + port: http + {{- end }} + {{- end }} + {{- if .Values.readinessProbeEnabled }} + readinessProbe: + {{- if .Values.readinessProbe }} + {{- with .Values.readinessProbe }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- else }} + httpGet: + path: {{ .Values.global.metacatUiWebRoot }} + port: http + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + - name: {{ .Release.Name }}-mcui-source-files + mountPath: /usr/share/nginx/html + {{- if .Values.customTheme.enabled }} + - name: {{ .Release.Name }}-mcui-custom-theme-files + mountPath: {{ include "metacatui.root.mountpath" . }}/js/themes/{{ .Values.global.metacatUiThemeName }} + {{- if .Values.customTheme.subPath }} + subPath: {{ .Values.customTheme.subPath }} + {{- end }} + {{- end }} + - name: {{ .Release.Name }}-mcui-config-js + mountPath: {{ include "metacatui.root.mountpath" . }}/config/config.js + subPath: config.js + volumes: + - name: {{ .Release.Name }}-mcui-config-js + configMap: + name: {{ .Release.Name }}-metacatui-config-js + defaultMode: 0644 + - name: {{ .Release.Name }}-mcui-source-files + {{- if eq $source "pvc" }} + persistentVolumeClaim: + claimName: {{ required "claimName_REQUIRED" .Values.source.pvc.sourceCodeClaimName }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.customTheme.enabled }} + - name: {{ .Release.Name }}-mcui-custom-theme-files + persistentVolumeClaim: + claimName: {{ required "claimName_REQUIRED" .Values.customTheme.claimName }} + {{- end }} + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/hpa.yaml b/helm/templates/hpa.yaml new file mode 100644 index 000000000..da1030a42 --- /dev/null +++ b/helm/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "metacatui.fullname" . }} + labels: + {{- include "metacatui.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "metacatui.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml new file mode 100644 index 000000000..93909a20d --- /dev/null +++ b/helm/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "metacatui.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "metacatui.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 000000000..785db5717 --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "metacatui.fullname" . }} + labels: + {{- include "metacatui.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "metacatui.selectorLabels" . | nindent 4 }} diff --git a/helm/templates/serviceaccount.yaml b/helm/templates/serviceaccount.yaml new file mode 100644 index 000000000..84f602059 --- /dev/null +++ b/helm/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "metacatui.serviceAccountName" . }} + labels: + {{- include "metacatui.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm/templates/tests/test-connection.yaml b/helm/templates/tests/test-connection.yaml new file mode 100644 index 000000000..98a51f6dc --- /dev/null +++ b/helm/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "metacatui.fullname" . }}-test-connection" + labels: + {{- include "metacatui.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "metacatui.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 000000000..eaaa4246a --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,253 @@ +## Default values for metacatui. +## This is a YAML-formatted file. +## Edit values, then install and/or upgrade using: +## +## $ helm upgrade --install releasename -n mynamespace ./relative/path/to/helm/directory +## + +global: + ## @param global.metacatUiThemeName The theme name to use. Required, even if overriding config.js + ## + metacatUiThemeName: "knb" + + ## @param global.metacatUiWebRoot The url root to be appended after the metacatui baseUrl. + ## Starts with "/". Required, even if overriding config.js + ## + metacatUiWebRoot: "/" + + ## @param global.metacatExternalBaseUrl metacat base url, accessible from outside the cluster. + ## Include protocol and trailing slash, but not the context; e.g.: "https://test.arcticdata.io/" + ## + metacatExternalBaseUrl: "https://localhost/" + + ## @param global.metacatAppContext The directory that metacat is installed in at the `baseUrl`. + ## Used to populate metacatui's 'metacatContext'. + ## For example, if you have metacat installed in $TOMCAT/webapps/knb, then this should be set + ## to "knb". If you are using the default metacat settings, it should be 'metacat' + ## + metacatAppContext: "metacat" + + ## @param global.d1ClientCnUrl the url of the CN. Used to determine metacatui's 'd1CNBaseUrl' + ## NOTE: only the base URL is used to populate d1CNBaseUrl; anything after the third "/" will be + ## ignored - e.g. if you set d1ClientCnUrl: "https://cn.dataone.org/cn", then d1CNBaseUrl will + ## be set to "https://cn.dataone.org/" + ## + d1ClientCnUrl: "https://cn.dataone.org/cn" + +## @section MetacatUI Configuration Files ('config.js') +## +## The k8s version of Metacatui requires two 'config.js' configuration files: +## 1. the "root config" at the path {root}/config/config.js, and +## 2. the "theme config" in the theme directory itself (for example, see the knb config.js at: +## https://github.com/NCEAS/metacatui/blob/main/src/js/themes/knb/config.js) +## +## The "root config" file must, at a minimum, contain the name of the theme to be used, e.g: +## +## MetacatUI.AppConfig = { +## theme: "knb" +## }; +## +## ...and metacatui will then load that theme and its "theme config". +## If any additional settings are defined in the "root config", Metacatui will use them to override +## corresponding settings in the "theme config". +## +## By default, this chart creates a simple "root config" that contains any values of the form: +## 'key: stringValue,' or 'key: intValue,' provided in the 'appConfig:' section below. If you need +## to provide more-complex overrides, set 'appConfig.enabled: false', below, and manually create +## your own configMap named 'YourReleaseName-metacatui-config-js', containing your custom config.js. +## +## Note that if you choose not to use one of the themes that are bundled with metacatui, you will +## need to provide the files for that theme (including its 'theme config' file) on a Persistent +## Volume. See the MetacatUI documentation for help with creating custom themes: +## https://nceas.github.io/metacatui/install/configuration/index.html +## + +## @param appConfig The MetacatUI.AppConfig optional override settings +## +## Optional configuration. Note you can define any attributes here, to override those that appear +## in config.js, provided they are of the form: 'key: stringValue,' or 'key: intValue,'. +## +## See full listing in AppModel.js: +## https://github.com/NCEAS/metacatui/blob/main/src/js/models/AppModel.js +## +## * * * IMPORTANT NOTE: * * * DO NOT SET THE FOLLOWING VALUES IN THIS SECTION! * * * +## They will be ignored here, because they are populated from the "global" section, above: +## * theme: uses '.Values.global.metacatUiThemeName' +## * root: uses '.Values.global.metacatUiWebRoot' +## * baseUrl: uses '.Values.global.metacatExternalBaseUrl' +## * d1CNBaseUrl: uses base URL portion of '.Values.global.d1ClientCnUrl' +## * metacatContext: uses '.Values.global.metacatAppContext' +## +appConfig: + ## @param appConfig.enabled Define theme name and override values in MetacatUI.AppConfig + ## If you need to provide more-complex overrides, set 'appConfig.enabled: false', and manually + ## create your own configMap named 'YourReleaseName-metacatui-config-js', containing your custom + ## config.js. + ## + ## IMPORTANT: global.metacatUiThemeName MUST be set to your theme name, even if you are disabling + ## appConfig and providing a custom config.js in a configMap. + ## + enabled: true + +## @param themeFilesClaimName Provide source files for a custom Theme (also see 'appConfig.theme'). +## NOTE that global.metacatUiThemeName MUST match the theme of your custom theme +## +customTheme: + ## @param customTheme.enabled Provide custom Theme files on a pre-configured PVC + ## + enabled: false + + ## @param customTheme.themeClaimName substitute your own PVC name + ## + claimName: "Your-Pre-Configured-pvc-name" + + ## @param customTheme.subPath path to theme directory, within mounted filesystem + ## For example, if you cloned https://github.com/NCEAS/metacatui-themes to the root of your + ## shared drive, it would look like this: + ## + ## /metacatui-themes + ## └── src + ## ├── cerp + ## │ └── js + ## │ └── themes + ## │ └── cerp + ## │ ├── config.js + ## │ ├── css + ## │ ├── ...etc + ## ├── drp + ## │ └── js + ## │ └── themes + ## │ └── drp + ## │ ├── config.js + ## │ ├── css + ## ...etc ├── ...etc + ## + ## ...so to use the drp theme, you would set the subPath to: + ## subPath: "metacatui-themes/src/drp/js/themes/drp" + ## + subPath: "" + +## @param livenessProbeEnabled Enable livenessProbe. Autoconfigured using .Values.appConfig.root +## To override autoconfig, keep 'livenessProbeEnabled: true', and define a livenessProbe; e.g: +## livenessProbe: +## httpGet: +## path: /myPath +## port: http +## +livenessProbeEnabled: true + +## @param readinessProbeEnabled Enable readinessProbe. Autoconfigured using .Values.appConfig.root +## To override autoconfig, keep 'readinessProbeEnabled: true', and define a readinessProbe; e.g: +## readinessProbe: +## httpGet: +## path: /myPath +## port: http +## +readinessProbeEnabled: true + +service: + type: ClusterIP + port: 80 + +## @param ingress typically disabled here (enabled: false) and handled by metacat helm chart. +## Example settings below for local dev +## +ingress: + enabled: false + className: traefik # enable in rancher desktop + annotations: + kubernetes.io/ingress.class: traefik + hosts: + - host: myMacbookPro.local # example hostname of local machine + paths: + - path: / + pathType: Prefix + backend: + service: + name: mcui-metacatui # Assumes the service is exposed in the 'mcui' namespace + port: + number: 80 + tls: [] + + +## @param source The source from which to retrieve the metacatui code. NOTE: Changes should not be +## needed here, unless you wish to deviate from the official metacatui release version defined in +## the helm chart (see Chart.yaml) +## +source: + ## @param source.from Options are "package" (the default), "git", or "pvc" + ## * "package" will download a release package from the metacatui git repository, unzip it, and + ## install the files in local pod storage (emptyDir{}) + ## * "git" will clone a specific branch or tag from the metacatui git repository, and install the + ## files in local pod storage (emptyDir{}) + ## * "pvc" expects to find a pre-configured PVC containing the files to be used. Note you will + ## need to provide values for a pre-configured PVC in 'volumes', below + ## + from: package + + ## @param source.package (default): Download the official release version defined in Chart.yaml + ## example: + ## source.from: package + ## source.package.location: "https://github.com/NCEAS/metacatui/archive" + ## source.package.version: "2.26.0" + ## ...will download: https://github.com/NCEAS/metacatui/archive/2.26.0.zip + ## + ## Note these settings ignored unless 'source.from:' is set to 'package' + ## + package: + ## @param source.package.location The remote location where the release zipfile is hosted + ## + location: "https://github.com/NCEAS/metacatui/archive" + + ## source.package.version override the release version defined in Chart.yaml + ## Assumes release is a zipfile named .zip + ## LEAVE UNSET, UNLESS YOU NEED TO OVERRIDE THE CHART SETTING! + version: "" + + ## @param source.git clone a specific branch or tag from the metacatui git repository, and install + ## the files in local pod storage (emptyDir{}) + ## + ## Note these settings ignored unless 'source.from:' is set to 'git' + ## + git: + ## @param source.git.repoUrl the https url of the repo to be cloned + repoUrl: "https://github.com/NCEAS/metacatui.git" + + ## @param source.git.revision Any string that makes sense after the command `git checkout`... + ## - for example: + ## revision: "tags/2.29.0" => git checkout tags/2.29.0 + ## revision: "develop" => git checkout develop + ## + revision: "develop" + + ## @param source.pvc Provide custom source files on a pre-configured PVC (typ. for development) + ## + ## Note these settings ignored unless 'source.from:' is set to 'pvc' + ## + pvc: + ## @param volumes.name substitute your own PVC name + ## + sourceCodeClaimName: "Your-Pre-Configured-pvc-name" + +replicaCount: 1 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} + +image: {} +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" +serviceAccount: {} +podAnnotations: {} +podLabels: {} +podSecurityContext: {} +securityContext: {} +resources: {}