diff --git a/.github/workflows/_helm.yml b/.github/workflows/_helm.yml new file mode 100644 index 000000000..ca1496f5a --- /dev/null +++ b/.github/workflows/_helm.yml @@ -0,0 +1,71 @@ +name: Package helm charts + +on: + workflow_call: + +env: + HELM_VERSION_TO_INSTALL: 3.14.3 + +jobs: + package-helm-charts: + name: Package and Push Helm Chart + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install helm + uses: Azure/setup-helm@v3 + with: + version: ${{ env.HELM_VERSION_TO_INSTALL }} + + # Check that alpha/beta versions have the form X.Y.Z-alpha.A requried by Helm. + # An early check saves waiting for the entire build before finding a problem. + - name: Check helm version tag + if: ${{ github.ref_type == 'tag' }} + env: + VERSION: "${{ github.ref_name }}" + run: | + if [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-alpha|-beta|-rc).*?$ ]]; then + echo "Valid version format: ${VERSION}" + else + echo "Invalid version: ${VERSION}. Expected: X.Y.Z or X.Y.Z-beta.1 or X.Y.Z-alpha.1" + exit 1 + fi + + - name: Package helm charts + env: + VERSION: "${{ github.ref_type == 'tag' && github.ref_name || '0.0.0' }}" + run: | + set -xe + + mkdir -p charts + for i in $(find Charts -type d -maxdepth 1 -mindepth 1); do + if [[ ${i} =~ ^.*-ioc$ ]]; then + echo "Skipping IOC schema chart: ${i}" + continue + fi + echo "Packaging chart: ${i}" + helm package -u --app-version ${VERSION} --version ${VERSION} ${i} + mv $(basename ${i})-*.tgz charts/ + done + + - name: Upload helm chart values schemas + uses: actions/upload-artifact@v4 + with: + name: helm-chart-schemas + path: schemas/* + + - name: Push tagged helm chart to registry + # TODO - switch to using https://github.com/helm/chart-releaser-action of maybe the docker action? + if: ${{ github.ref_type == 'tag' }} + run: | + set -x + + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io/${{ github.repository_owner }} --username ${{ github.repository_owner }} --password-stdin + REGISTRY=oci://ghcr.io/${{github.repository_owner }}/charts + for i in charts/*.tgz; do + helm push "${i}" ${REGISTRY,,} + done diff --git a/.github/workflows/_tox.yml b/.github/workflows/_tox.yml index a13536d3a..f01518107 100644 --- a/.github/workflows/_tox.yml +++ b/.github/workflows/_tox.yml @@ -18,5 +18,8 @@ jobs: - name: Install python packages uses: ./.github/actions/install_requirements + - name: Install helm plugins + run: helm plugin install https://github.com/losisin/helm-values-schema-json.git + - name: Run tox run: tox -e ${{ inputs.tox }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9529f09e4..be681822c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,16 +44,22 @@ jobs: if: needs.check.outputs.branch-pr == '' uses: ./.github/workflows/_dist.yml + helm: + uses: ./.github/workflows/_helm.yml + permissions: + contents: read + packages: write + pypi: if: github.ref_type == 'tag' - needs: dist + needs: [helm, dist] uses: ./.github/workflows/_pypi.yml permissions: id-token: write release: if: github.ref_type == 'tag' - needs: [dist, docs] + needs: [dist, docs, helm] uses: ./.github/workflows/_release.yml permissions: contents: write diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60fc23f9a..80e22ba90 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,10 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-yaml + exclude: ^Charts/ - id: check-merge-conflict - id: end-of-file-fixer @@ -22,3 +23,11 @@ repos: entry: ruff format --force-exclude types: [python] require_serial: true + + - repo: https://github.com/losisin/helm-values-schema-json + rev: v2.2.1 + hooks: + - id: helm-schema + args: + - --config + - Charts/fastcs/.schema.config.yaml diff --git a/Charts/README.md b/Charts/README.md new file mode 100644 index 000000000..8d08d850a --- /dev/null +++ b/Charts/README.md @@ -0,0 +1,70 @@ +# Charts + +This directory contains Helm charts for deploying FastCS services. + +## Github Actions + +This folder works in tandem with [_helm.yml](../.github/workflows/_helm.yml) github actions workflow which: + +- Validates the stricter form of SemVer tag that helm requires. +- Finds all subfolders in the `Charts` directory and packages them as Helm charts. +- Publishes the packaged charts to ghcr.io/${{github.repository_owner }}/charts/CHART_NAME +- Uploads the contents of /schemas to artifacts (meaning they will be published to the release) + +This standalone Helm related workflow is independent of the rest of the workflows except that the _pypi workflow has _helm added to its `needs` in `ci.yml`, making sure we only publish to pypi with valid SemVer tags. + +## Schema Generation + +Schema generation for charts' `values.yaml` is handled by [helm-values-schema-json](https://github.com/losisin/helm-values-schema-json). Which is in turn controlled by annotations in the default [values.yaml](fastcs/values.yaml) file. + +The generated schema file will be called `values.schema.json` and will be placed in the same directory as the `values.yaml` file and commited to the repo. This is done automatically by a [pre-commit hook](https://github.com/DiamondLightSource/FastCS/blob/8232393b38cc8e0eee00680e95c2ce06e7983ba6/.pre-commit-config.yaml#L27-L33). Therefore, when developing charts you can update schemas with: + +```bash +git add . ; pre-commit +# or +pre-commit run --all-files +``` + + +Note that this standard name for the schema file means that it is packaged up with the helm chart and available for schema checks in ArgoCD for example. + +## `schemas` folder + +The schemas folder allows us to declare the schemas we want to publish to the release. + +It should contain: + +- A symlink to each of the `values.schema.json` files in the subfolders of `Charts`. The symlink should have a unique name, e.g. `fastcs-values.schema.json`, thus allowing multiple schemas to be published per repo. +- A service schema file which references the above and can be used to validate `values.yaml` in epics-containers services repos, where the these charts will be used as sub-charts. e.g. [fastcs-service.schema.json](../schemas/fastcs-service.schema.json) + +The service schema files are hand coded as they are extremely simple and unlikely to change. + +## Debuging/Development In-Cluster + +The `fastcs` helm chart has two variables to enable debugging/development in-cluster: + +- `editable`: When true: + - a PVC is created. + - The debug version of the container image is referenced. + - The contents of /workspaces and /venv are copied into the PVC. + - The venv from the debug image is an editable install of the project source code in /workspaces. + - The PVC folders are mounted over the corresponding folders in the container. +- `autostart`: + - When false, the container starts with PID 1 as sleep infinity. + - When true, the container starts with its normal entrypoint. + +In combination these flags can be used to debug or develop in-cluster. + +These features will be accessed via `ec`. See https://github.com/epics-containers/edge-containers-cli/issues/207 + +This script will: + +- Inspect the values of `editable` and `autostart` in the `values.yaml` file of the specified IOC (TODO: at present it uses the p47-services source code to do so but this should be determined from the cluster in future). +- Port forward the debugpy port (5678) from the pod to localhost. +- If editable is true, it will mount the PVC locally using pv-mounter and open VSCode to the /workspaces/xxx folder. +- If autostart is false, it will exec into the container and launch debugpy to run the main program. +- If autostart is true, it will exec into the container and attach debugpy to PID 1. + +This then allows you to attach VSCode to debugpy in the cluster container and if 'editable' is true, edit the source code in VSCode and have the changes reflected. + +To attach to debugpy the following launch.json configuration is supplied in the [fastcs-example project](https://github.com/DiamondLightSource/fastcs-example/blob/77daed5f5a2bd01ab4c0e1d8c812e8754b254674/.vscode/launch.json#L7-L22). (this will also go in python-copier-template in future). diff --git a/Charts/fastcs/.helmignore b/Charts/fastcs/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/Charts/fastcs/.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/Charts/fastcs/.schema.config.yaml b/Charts/fastcs/.schema.config.yaml new file mode 100644 index 000000000..95883aa67 --- /dev/null +++ b/Charts/fastcs/.schema.config.yaml @@ -0,0 +1,26 @@ +# .schema.yaml + +# Define input, output and source for $refs relative to repository root for pre-commit +values: + - Charts/fastcs/values.yaml + - Charts/fastcs/extra.values.yaml + +output: Charts/fastcs/values.schema.json + +# bundle up references (don't do this as K8S refs are 500kb+) +bundleRoot: Charts +bundle: false + +# Include comments for the helm-docs plugin into the schema, to allow e.g. documentation in VSCode +useHelmDocs: false + +# Allow additional properties for eg. initResources, different types of volumes/volumeMounts +noAdditionalProperties: false + +schemaRoot: + title: FastCS Helm chart + description: Helm chart for deploying a FastCS application + # No additional properties in schema root for tighter protection: + additionalProperties: false + +k8sSchemaVersion: v1.33.3 diff --git a/Charts/fastcs/Chart.yaml b/Charts/fastcs/Chart.yaml new file mode 100644 index 000000000..eee8b3b42 --- /dev/null +++ b/Charts/fastcs/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: fastcs +description: A Helm chart for FastCS Applications + +# 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.1.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: "1.16.0" diff --git a/Charts/fastcs/extra.values.yaml b/Charts/fastcs/extra.values.yaml new file mode 100644 index 000000000..49e115d11 --- /dev/null +++ b/Charts/fastcs/extra.values.yaml @@ -0,0 +1,15 @@ +################################################################################ +# values for lists of custom objects from values.yaml for schema generation +################################################################################ + +# @schema description: Add arbitrary containers to the pod +extraContainers: + # @schema: description: A name for the additional container + - name: container_name + # @schema $ref: $k8s/container.json#/properties/image + image: image_uri + # @schema $ref: $k8s/container.json#/properties/command + command: + - command + - arg1 + - arg2 diff --git a/Charts/fastcs/templates/_helpers.tpl b/Charts/fastcs/templates/_helpers.tpl new file mode 100644 index 000000000..3a598e647 --- /dev/null +++ b/Charts/fastcs/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "fastcs.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 "fastcs.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 "fastcs.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "fastcs.labels" -}} +helm.sh/chart: {{ include "fastcs.chart" . }} +{{ include "fastcs.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "fastcs.selectorLabels" -}} +app.kubernetes.io/name: {{ include "fastcs.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/Charts/fastcs/templates/clusterIP.tpl b/Charts/fastcs/templates/clusterIP.tpl new file mode 100644 index 000000000..e1b43bec9 --- /dev/null +++ b/Charts/fastcs/templates/clusterIP.tpl @@ -0,0 +1,73 @@ + +{{- define "allocateIpFromName" -}} + {{- $name := printf "%s.%s" .name .namespace -}} + {{- $baseIpWithCIDR := .baseIp -}} + + {{- $startIp := .startIp | int -}} + {{- $conversion := atoi (adler32sum $name) -}} + + {{- $baseIpParts := split "/" $baseIpWithCIDR -}} + {{- $baseIp := index $baseIpParts "_0" -}} + {{- $cidrRange := index $baseIpParts "_1" | int -}} + + {{- $octets := split "." $baseIp -}} + {{- $firstOctet := index $octets "_0" | int -}} + {{- $secondOctet := index $octets "_1" | int -}} + {{- $thirdOctet := index $octets "_2" | int -}} + {{- $fourthOctet := index $octets "_3" | int -}} + + + {{- $totalIps := 1 }} + {{- $loopcnt:= sub 32 $cidrRange -}} + {{- range $i,$k := until ($loopcnt | int) }} + {{- $totalIps = mul $totalIps 2 }} + {{- end }} + + {{- $ipSuffix := add $startIp (mod $conversion $totalIps) -}} + + {{- $secondOctet := add $secondOctet (div $ipSuffix 65536) -}} + {{- $ipSuffix = mod $ipSuffix 65536 -}} + {{- $thirdOctet := add $thirdOctet (div $ipSuffix 256) -}} + {{- $fourthOctet := mod $ipSuffix 256 -}} + + {{- if gt $fourthOctet 255 }} + {{- $fourthOctet = mod $fourthOctet 256 -}} + {{- end }} + {{- if gt $thirdOctet 255 }} + {{- $thirdOctet = mod $thirdOctet 256 -}} + {{- $secondOctet = add $secondOctet 1 -}} + {{- end }} + {{- if gt $secondOctet 255 }} + {{- $secondOctet = mod $secondOctet 256 -}} + {{- end }} + + {{- printf "%d.%d.%d.%d" $firstOctet $secondOctet $thirdOctet $fourthOctet -}} +{{- end -}} + + +{{- define "allocateIpFromNames" -}} + {{- $name := printf "%s.%s" .name .namespace -}} + {{- $baseIpWithCIDR := .baseIp -}} + + {{- $startIp := .startIp | int -}} + {{- $conversion := atoi (adler32sum $name) -}} + + {{- $baseIpParts := split "/" $baseIpWithCIDR -}} + {{- $baseIp := index $baseIpParts "_0" -}} + {{- $cidrRange := index $baseIpParts "_1" | int -}} + + {{- $octets := split "." $baseIp -}} + {{- $firstOctet := index $octets "_0" | int -}} + {{- $secondOctet := index $octets "_1" | int -}} + {{- $thirdOctet := index $octets "_2" | int -}} + {{- $fourthOctet := index $octets "_3" | int -}} + + {{- $totalIps := 1 }} + {{- $loopcnt:= sub 32 $cidrRange -}} + {{- range $i,$k := until ($loopcnt | int) }} + {{- $totalIps = mul $totalIps 2 }} + {{- end }} + {{- printf "CIDR %d IPs %d" $cidrRange $totalIps -}} + + +{{- end -}} diff --git a/Charts/fastcs/templates/pvcs.yaml b/Charts/fastcs/templates/pvcs.yaml new file mode 100644 index 000000000..1a2ce3737 --- /dev/null +++ b/Charts/fastcs/templates/pvcs.yaml @@ -0,0 +1,20 @@ +# PVC for debugging volume +{{ if .Values.editable }} +{{- $location := default .Values.global.location .Values.location | required "ERROR - You must supply location or global.location" -}} +{{- $ioc_group := default .Values.global.ioc_group .Values.ioc_group | required "ERROR - You must supply ioc_group or global.ioc_group" -}} + +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-develop + labels: + app: {{ .Release.Name }} + location: {{ $location }} + ioc_group: {{ $ioc_group }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: "1Gi" +{{- end }} diff --git a/Charts/fastcs/templates/service.yaml b/Charts/fastcs/templates/service.yaml new file mode 100644 index 000000000..4073818fc --- /dev/null +++ b/Charts/fastcs/templates/service.yaml @@ -0,0 +1,51 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} + labels: + {{- include "fastcs.labels" . | nindent 4 }} + app: {{ .Release.Name }} + location: {{ .Values.global.location }} + ioc_group: {{ .Values.global.ioc_group }} + is_ioc: "true" +spec: + selector: + app: {{ .Release.Name }} + {{- include "fastcs.selectorLabels" . | nindent 4 }} + type: {{ .Values.service.type }} + # allocate a fixed clusterIP for this service based on the service name + {{- $alloc_args := dict "name" .Release.Name "namespace" .Release.Namespace "baseIp" .Values.baseIp "startIp" .Values.startIp }} + clusterIP: {{ .Values.clusterIP | default (include "allocateIpFromName" $alloc_args) }} + ports: + - name: ca-server-tcp + port: {{ .Values.ca_server_port | default 5064 }} + targetPort: {{ .Values.ca_server_port | default 5064 }} + protocol: TCP + - name: ca-server-udp + port: {{ .Values.ca_server_port | default 5064 }} + targetPort: {{ .Values.ca_server_port | default 5064 }} + protocol: UDP + - name: ca-repeater-tcp + port: {{ add1 (.Values.ca_server_port | default 5064) }} + targetPort: {{ add1 (.Values.ca_server_port | default 5064) }} + protocol: TCP + - name: ca-repeater-udp + port: {{ add1 (.Values.ca_server_port | default 5064) }} + targetPort: {{ add1 (.Values.ca_server_port | default 5064) }} + protocol: UDP + - name: pva-server-tcp + port: {{ .Values.pva_server_port | default 5075 }} + targetPort: {{ .Values.pva_server_port | default 5075 }} + protocol: TCP + - name: pva-server-udp + port: {{ .Values.pva_server_port | default 5075 }} + targetPort: {{ .Values.pva_server_port | default 5075 }} + protocol: UDP + - name: pva-broadcast-tcp + port: {{ add1 (.Values.pva_server_port | default 5075) }} + targetPort: {{ add1 (.Values.pva_server_port | default 5075) }} + protocol: TCP + - name: pva-broadcast-udp + port: {{ add1 (.Values.pva_server_port | default 5075) }} + targetPort: {{ add1 (.Values.pva_server_port | default 5075) }} + protocol: UDP diff --git a/Charts/fastcs/templates/statefulset.yaml b/Charts/fastcs/templates/statefulset.yaml new file mode 100644 index 000000000..4a3170d96 --- /dev/null +++ b/Charts/fastcs/templates/statefulset.yaml @@ -0,0 +1,180 @@ +{{- /* +Default the derivable substitution values. + +This keeps the length of the values.txt file for each individual IOC +to a minimum +*/ -}} +{{- $location := default .Values.global.location .Values.location | required "ERROR - You must supply location or global.location" -}} +{{- $ioc_group := default .Values.global.ioc_group .Values.ioc_group | required "ERROR - You must supply ioc_group or global.ioc_group" -}} +{{- $opisClaim := default (print $ioc_group "-opi-claim") .Values.opisClaim -}} +{{- $runtimeClaim := default (print $ioc_group "-runtime-claim") .Values.runtimeClaim -}} +{{- $autosaveClaim := default (print $ioc_group "-autosave-claim") .Values.autosaveClaim -}} +{{- $image := .Values.image | required "ERROR - You must supply image." -}} + +{{- $enabled := eq .Values.global.enabled false | ternary false true -}} + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }} + labels: + location: {{ $location }} + ioc_group: {{ $ioc_group }} + enabled: {{ $enabled | quote }} + is_ioc: "true" + {{- include "fastcs.labels" . | nindent 4 }} +spec: + replicas: {{ $enabled | ternary 1 0 }} + selector: + matchLabels: + {{- include "fastcs.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + location: {{ $location }} + ioc_group: {{ $ioc_group }} + enabled: {{ $enabled | quote }} + is_ioc: "true" + {{- include "fastcs.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + # re-deploy in case the configMap has changed - use a random value + # unless the Commit Hash is supplied (by ArgoCD or helm command line) + rollme: {{ .Values.global.commitHash | default (randAlphaNum 5) | quote }} + spec: + terminationGracePeriodSeconds: 2 + hostNetwork: {{ .Values.hostNetwork }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.editable }} + # add in an init container to copy out virtual environment and workspaces + initContainers: + - name: {{ .Release.Name }}-init-debug + image: "{{ .Values.image.repository }}-debug:{{ .Values.image.tag }}" + imagePullPolicy: IfNotPresent + resources: + {{- .Values.initResources | default .Values.resources | toYaml | nindent 12 }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.initCommand }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ .Release.Name }}-develop + mountPath: /dest + {{- end }} + containers: + {{- range .Values.extraContainers }} + - name: {{ .name }} + image: {{ .image }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + {{- with $.Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .command }} + command: + {{- . | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + {{- with $.Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + - name: opis-volume + mountPath: /epics/opi + subPath: "{{ $.Release.Name }}" + - name: config-volume + mountPath: {{ $.Values.iocConfig }} + {{- if $.Values.editable }} + - name: {{ $.Release.Name }}-develop + mountPath: /dest + {{- end }} + {{- end }} + - name: {{ .Chart.Name }} + image: '{{ .Values.image.repository }}{{ ternary "-debug" "" .Values.editable }}:{{ .Values.image.tag }}' + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.autostart }} + {{- with .Values.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- else }} + {{- with .Values.debugCommand }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + {{- with .livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + - name: opis-volume + mountPath: /epics/opi + subPath: "{{ .Release.Name }}" + - name: config-volume + mountPath: {{ .Values.iocConfig }} + {{- if .Values.editable }} + - name: {{ .Release.Name }}-develop + mountPath: /workspaces + subPath: workspaces + - name: {{ .Release.Name }}-develop + mountPath: /venv + subPath: venv + {{- end }} + volumes: + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: opis-volume + persistentVolumeClaim: + claimName: {{ $opisClaim }} + - name: config-volume + configMap: + name: {{ .Release.Name }}-config + {{- if .Values.editable }} + - name: {{ .Release.Name }}-develop + persistentVolumeClaim: + claimName: {{ .Release.Name }}-develop + {{- 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/Charts/fastcs/values.schema.json b/Charts/fastcs/values.schema.json new file mode 100644 index 000000000..88c13160f --- /dev/null +++ b/Charts/fastcs/values.schema.json @@ -0,0 +1,171 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FastCS Helm chart", + "description": "Helm chart for deploying a FastCS application", + "type": "object", + "properties": { + "affinity": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/affinity.json", + "type": "object" + }, + "args": { + "description": "command args to pass to the production container", + "type": "array" + }, + "autostart": { + "description": "editable and autostart are used for debugging and development in-cluster", + "type": "boolean" + }, + "baseIp": { + "description": "CIDR for services addresses.", + "type": "string", + "pattern": "^(\\d{1,3}\\.){3}\\d{1,3}\\/\\d{1,2}$" + }, + "ca_server_port": { + "description": "service port for Channel Access", + "type": "integer" + }, + "clusterIP": { + "description": "Override for the cluster IP - only needed if allocateIpFromName clashes", + "type": "null", + "pattern": "^(\\d{1,3}\\.){3}\\d{1,3}$" + }, + "command": { + "description": "command to run for the production container", + "type": "array" + }, + "debugCommand": { + "description": "command to run for the debugging (non- autostart) container", + "type": "array", + "items": { + "type": "string" + } + }, + "editable": { + "description": "editable and autostart are used for debugging and development in-cluster", + "type": "boolean" + }, + "extraContainers": { + "description": "Add arbitrary containers to the pod", + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/command", + "type": "array", + "items": { + "type": "string" + } + }, + "image": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/image", + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "global": { + "description": "shared values for all services", + "type": "object", + "additionalProperties": true + }, + "hostNetwork": { + "description": "enable host networking for the pod", + "type": "boolean" + }, + "image": { + "description": "container image URI", + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "initCommand": { + "description": "command to run in the init container for editable mode", + "type": "array", + "items": { + "type": "string" + } + }, + "iocConfig": { + "description": "path to the location of config folder (defaults to be the same as C++ IOCs)", + "type": "string" + }, + "livenessProbe": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/livenessProbe", + "type": "object" + }, + "nodeSelector": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/podspec.json#/properties/nodeSelector", + "type": "object" + }, + "podAnnotations": { + "description": "Add annotations to the pod", + "type": "object" + }, + "podLabels": { + "description": "Add labels to the pod", + "type": "object" + }, + "podSecurityContext": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/podspec.json#/properties/securityContext", + "type": "object" + }, + "pva_server_port": { + "description": "service port for PV Access", + "type": "integer" + }, + "readinessProbe": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/readinessProbe", + "type": "object" + }, + "resources": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/resources", + "type": "object" + }, + "securityContext": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/securityContext", + "type": "object" + }, + "service": { + "description": "The service will be configured for Channel Access and PVA.", + "type": "object", + "properties": { + "ca_port": { + "type": "integer" + }, + "pva_port": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "tolerations": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/podspec.json#/properties/tolerations", + "type": "array" + }, + "volumeMounts": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/container.json#/properties/volumeMounts", + "type": "array" + }, + "volumes": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.3/podspec.json#/properties/volumes", + "type": "array" + } + }, + "additionalProperties": false +} diff --git a/Charts/fastcs/values.yaml b/Charts/fastcs/values.yaml new file mode 100644 index 000000000..9ade52dc4 --- /dev/null +++ b/Charts/fastcs/values.yaml @@ -0,0 +1,126 @@ +# yaml-language-server: $schema=values.schema.json + +# NOTE: the $k8s schema references come from +# https://github.com/yannh/kubernetes-json-schema/tree/master/v1.33.1 +# with the version set in .fastcs.schema.config.yaml (k8sSchemaVersion) + +# Default values for fastcs-instance chart. +# With annotations for building the schema using: +# https://github.com/losisin/helm-values-schema-json.git + +# @schema description: shared values for all services +# @schema additionalProperties: true +global: {} + +# @schema description: container image URI +image: + repository: "" + pullPolicy: IfNotPresent + tag: "" + +# @schema description: command to run for the production container +command: [] + +# @schema description: command args to pass to the production container +args: [] + +# @schema description: command to run for the debugging (non- autostart) container +debugCommand: + - /bin/bash + - -c + - "sleep infinity" + +# @schema description: command to run in the init container for editable mode +initCommand: + - /bin/bash + - -c + - | + echo "Running as account"; id + if [ -d /dest/venv ]; then + echo "Virtual environment already exists, skipping copy" + else + echo "Copying virtual env to the debugging volume" + cp -r /venv /dest/venv + echo "Copying workspaces to the debugging volume" + cp -r /workspaces /dest/workspaces + echo "Setting permissions on the debugging volume" + chmod -R o+rwX /dest/venv /dest/workspaces + fi + echo "Init container completed successfully" + +# @schema description: enable host networking for the pod +hostNetwork: true + +# @schema description: editable and autostart are used for debugging and development in-cluster +# editable: +# creates a PVC with /venv and /workspaces mounted into the container +# runs an init container with entrypoint initCommand above +editable: false +# @schema description: editable and autostart are used for debugging and development in-cluster +# autostart: +# false: entrypoint is debugCommand above +# true: entrypoint is command above +autostart: true + +# @schema description: path to the location of config folder (defaults to be the same as C++ IOCs) +iocConfig: /epics/ioc/config + +# @schema description: service port for Channel Access +ca_server_port: 5064 +# @schema description: service port for PV Access +pva_server_port: 5075 + +# @schema description: CIDR for services addresses. +# Used by allocateIpFromName to allocate a fixed cluster IP for the service. +# The default is the same for all DLS clusters. +baseIp: 10.96.0.0/12 # @schema pattern:^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$ + +# @schema description: Override for the cluster IP - only needed if allocateIpFromName clashes +clusterIP: # @schema pattern:^(\d{1,3}\.){3}\d{1,3}$ + +# @schema description: The service will be configured for Channel Access and PVA. +# Here you can override the ports or make this a LoadBalancer service if required. +service: + type: ClusterIP + ca_port: 5064 + pva_port: 5075 + +# Add additional containers specified by image and command +# see extra.values.yaml for the schema of this list of objects +extraContainers: [] + +# @schema description: Add annotations to the pod +podAnnotations: {} + +# @schema description: Add labels to the pod +podLabels: {} + +# @schema $ref: $k8s/podspec.json#/properties/securityContext +podSecurityContext: {} + +# @schema $ref:$k8s/container.json#/properties/securityContext +securityContext: {} + +# @schema $ref: $k8s/container.json#/properties/resources +resources: {} + +# @schema $ref: $k8s/container.json#/properties/livenessProbe +livenessProbe: {} + +# @schema $ref: $k8s/container.json#/properties/readinessProbe +readinessProbe: {} + +# @schema $ref: $k8s/podspec.json#/properties/volumes +volumes: [] + +# @schema $ref: $k8s/container.json#/properties/volumeMounts +volumeMounts: [] + +# @schema $ref: $k8s/podspec.json#/properties/tolerations +tolerations: [] + +# @schema $ref: $k8s/podspec.json#/properties/nodeSelector +nodeSelector: {} + +# @schema $ref: $k8s/affinity.json +affinity: {} diff --git a/Dockerfile b/Dockerfile index 35d2abf7a..e9e61b9a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ graphviz \ && rm -rf /var/lib/apt/lists/* +# Install helm for the dev container. This is the recommended +# approach per the docs: https://helm.sh/docs/intro/install +RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \ + chmod 700 get_helm.sh && \ + ./get_helm.sh && \ + rm get_helm.sh +RUN helm plugin install https://github.com/losisin/helm-values-schema-json.git + # Set up a virtual environment and put it in PATH RUN python -m venv /venv ENV PATH=/venv/bin:$PATH diff --git a/schemas/fastcs-service.schema.json b/schemas/fastcs-service.schema.json new file mode 100644 index 000000000..914325484 --- /dev/null +++ b/schemas/fastcs-service.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "fastcs": { + "$ref": "fastcs-values.schema.json", + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schemas/fastcs-values.schema.json b/schemas/fastcs-values.schema.json new file mode 120000 index 000000000..80f78888e --- /dev/null +++ b/schemas/fastcs-values.schema.json @@ -0,0 +1 @@ +../Charts/fastcs/values.schema.json \ No newline at end of file