diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index e6c09f6b..474f4486 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -49,10 +49,15 @@ jobs: - name: Run chart-testing (lint) run: ct lint --debug --config ./.github/configs/ct-lint.yaml --lint-conf ./.github/configs/lintconf.yaml + - name: Run helm unittest + run: | + helm plugin install https://github.com/quintush/helm-unittest + helm unittest charts/node-red -3 + - name: Create kind cluster uses: helm/kind-action@94729529f85113b88f4f819c17ce61382e6d8478 # renovate: tag=v1.2.0 if: steps.list-changed.outputs.changed == 'true' - name: Run chart-testing (install) run: ct install --config ./.github/configs/ct-lint.yaml - if: steps.list-changed.outputs.changed == 'true' \ No newline at end of file + if: steps.list-changed.outputs.changed == 'true' diff --git a/charts/node-red/.helmignore b/charts/node-red/.helmignore index 0e8a0eb3..2d9d1421 100644 --- a/charts/node-red/.helmignore +++ b/charts/node-red/.helmignore @@ -21,3 +21,4 @@ .idea/ *.tmproj .vscode/ +tests diff --git a/charts/node-red/Chart.yaml b/charts/node-red/Chart.yaml index c549a7da..95ba9ae6 100644 --- a/charts/node-red/Chart.yaml +++ b/charts/node-red/Chart.yaml @@ -9,7 +9,7 @@ icon: https://nodered.org/about/resources/media/node-red-icon-2.png type: application -version: 0.14.0 +version: 0.15.0 appVersion: 2.2.2 keywords: @@ -29,7 +29,7 @@ maintainers: annotations: artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: | - - add annotation support for deployment manifest + - add k8s-sidecar support for node-red settings reloading artifacthub.io/images: | - name: node-red image: docker.io/nodered/node-red:2.2.2 diff --git a/charts/node-red/README.md b/charts/node-red/README.md index 9b2101c2..118b8f47 100644 --- a/charts/node-red/README.md +++ b/charts/node-red/README.md @@ -1,6 +1,6 @@ # node-red ⚙ -![Version: 0.14.0](https://img.shields.io/badge/Version-0.14.0-informational?style=for-the-badge) +![Version: 0.15.0](https://img.shields.io/badge/Version-0.15.0-informational?style=for-the-badge) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=for-the-badge) ![AppVersion: 2.2.2](https://img.shields.io/badge/AppVersion-2.2.2-informational?style=for-the-badge) @@ -9,7 +9,7 @@ -## Description +## Description 📜 A Helm chart for Node-Red, a low-code programming for event-driven applications @@ -22,11 +22,11 @@ helm repo add node-red https://schwarzit.github.io/node-red-chart/ helm repo update ``` -### Installing the Chart +### Installing the Chart 📦 To install the chart with the release name node-red run: ```bash -helm install node-red node-red/node-red --version 0.14.0 +helm install node-red node-red/node-red --version 0.15.0 ``` After a few seconds, node-red should be running. @@ -40,7 +40,7 @@ helm install node-red node-red/node-red --namespace node-red > **Tip**: List all releases using `helm list`, a release is a name used to track a specific deployment -### Uninstalling the Chart +### Uninstalling the Chart 🗑️ To uninstall the `node-red` deployment: @@ -58,6 +58,7 @@ The command removes all the Kubernetes components associated with the chart and | deploymentAnnotations | object | `{}` | Deployment annotations | | deploymentStrategy | string | `""` | Specifies the strategy used to replace old Pods by new ones, default: `RollingUpdate` | | env | list | `[]` | node-red env, see more environment variables in the [node-red documentation](https://nodered.org/docs/getting-started/docker) | +| extraSidecars | list | `[]` | You can configure extra sidecars containers to run alongside the node-red pod. default: [] | | extraVolumeMounts | string | `nil` | Extra Volume Mounts for the node-red pod | | extraVolumes | string | `nil` | Extra Volumes for the pod | | fullnameOverride | string | `""` | String to fully override "node-red.fullname" | @@ -69,13 +70,13 @@ The command removes all the Kubernetes components associated with the chart and | ingress.annotations | object | `{}` | Additional ingress annotations | | ingress.className | string | `""` | Defines which ingress controller will implement the resource | | ingress.enabled | bool | `false` | Enable an ingress resource for the server | -| ingress.hosts[0] | object | `{"host":"chart-example.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}` | Ingress accepted hostnames | +| ingress.hosts[0].host | string | `"chart-example.local"` | | | ingress.hosts[0].paths[0] | object | `{"path":"/","pathType":"ImplementationSpecific"}` | The base path | | ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | Ingress type of path | | ingress.tls | list | `[]` | Ingress TLS configuration | | initContainers | list | `[]` | containers which are run before the app containers are started | -| metrics | object | `{"enabled":false,"path":"/metrics","serviceMonitor":{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabelings":[],"namespace":"","relabelings":[],"selector":{}}}` | Enable Service-Monitor for node-red | | metrics.enabled | bool | `false` | Deploy metrics service | +| metrics.path | string | `"/metrics"` | | | metrics.serviceMonitor.additionalLabels | object | `{}` | Prometheus ServiceMonitor labels | | metrics.serviceMonitor.enabled | bool | `false` | Enable a prometheus ServiceMonitor | | metrics.serviceMonitor.interval | string | `"30s"` | Prometheus ServiceMonitor interval | @@ -101,7 +102,22 @@ The command removes all the Kubernetes components associated with the chart and | serviceAccount.create | bool | `true` | Create service account | | serviceAccount.name | string | `""` | Service account name to use, when empty will be set to created account if | | settings | object | `{}` | You can configure node-red using a settings file. default: {} | -| sidecars | list | `[]` | You can configure a sidecar containers to run alongside the node-red pod. default: [] | +| sidecar.enabled | bool | `true` | Enable the sidecar | +| sidecar.env.label | string | `"node-red-settings"` | Label that should be used for filtering | +| sidecar.env.label_value | string | `"1"` | The value for the label you want to filter your resources on. Don't set a value to filter by any value | +| sidecar.env.method | string | `"watch"` | If METHOD is set to LIST, the sidecar will just list config-maps/secrets and exit. With SLEEP it will list all config-maps/secrets, then sleep for SLEEP_TIME seconds. Anything else will continuously watch for changes (see https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes). | +| sidecar.env.password | string | `"x"` | Password as key value pair | +| sidecar.env.passwordFromExistingSecret | object | `{}` | Password from existing secret | +| sidecar.env.script | string | `"flow_refresh.sh"` | Absolute path to shell script to execute after a configmap got reloaded. | +| sidecar.env.username | string | `"x"` | | +| sidecar.extraEnv | list | `[]` | Extra Environments for the sidecar | +| sidecar.image.pullPolicy | string | `"IfNotPresent"` | The image pull policy, default: `IfNotPresent` | +| sidecar.image.registry | string | `"quay.io"` | The image registry to pull the sidecar from | +| sidecar.image.repository | string | `"kiwigrid/k8s-sidecar"` | The image repository to pull from | +| sidecar.image.tag | string | `"1.15.9"` | The image tag to pull, default: ` 1.15.9` | +| sidecar.resources | object | `{}` | Resources for the sidecar | +| sidecar.securityContext | object | `{}` | Security context for the sidecar | +| sidecar.volumeMounts | list | `[]` | The extra volume mounts for the sidecar | | tolerations | list | `[]` | Toleration labels for pod assignment | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, @@ -118,7 +134,7 @@ helm install node-red node-red/node-red -f values.yaml > **Tip**: You can use the default [values.yaml](values.yaml) -## Monitoring +## Monitoring 🌡️ To enable the node-red prometheus monitoring capability, you need to install the node `node-red-contrib-prometheus-exporter`. For more details see [official documentation](https://flows.nodered.org/node/node-red-contrib-prometheus-exporter) @@ -131,13 +147,23 @@ metrics: enabled: true ``` +## Sidecar 🏎️ + +This Chart supports the handling for loading flows from configmaps/secrets via the [k8s-sidecar](https://github.com/kiwigrid/k8s-sidecar) + +You just need to create a configmap/secret with your `node-red` flow.json and annotate it with the a label and value defined in the chart `sidecar`. +Default values are: `node-red-settings:1`. + +The `k8s-sidecar` will then call the `node-red` api to reload the flows. This will be done via a script. To run this script successfully you need to provide the `username` and `password` +of your admin user. The admin user needs to have the right to use the `node-red` API. + ## Contributing 🤝 ### Contributing via GitHub Feel free to join. Checkout the [contributing guide](CONTRIBUTING.md) -## License +## License ⚖️ Apache License, Version 2.0 @@ -150,4 +176,4 @@ Apache License, Version 2.0 | Name | Email | Url | | ---- | ------ | --- | | dirien | engin.diri@mail.schwarz | https://jobs.schwarz | -| Kaktor | felix.kammerer@mail.schwarz | https://jobs.schwarz | \ No newline at end of file +| Kaktor | felix.kammerer@mail.schwarz | https://jobs.schwarz | diff --git a/charts/node-red/README.md.gotmpl b/charts/node-red/README.md.gotmpl index 48ad4837..51bf1ff9 100644 --- a/charts/node-red/README.md.gotmpl +++ b/charts/node-red/README.md.gotmpl @@ -10,7 +10,7 @@ -## Description +## Description 📜 {{ template "chart.description" . }} @@ -23,7 +23,7 @@ helm repo add node-red https://schwarzit.github.io/node-red-chart/ helm repo update ``` -### Installing the Chart +### Installing the Chart 📦 To install the chart with the release name node-red run: ```bash @@ -35,13 +35,13 @@ After a few seconds, node-red should be running. To install the chart in a specific namespace use following commands: ```bash -kubectl create ns node-red +kubectl create ns node-red helm install node-red node-red/node-red --namespace node-red ``` > **Tip**: List all releases using `helm list`, a release is a name used to track a specific deployment -### Uninstalling the Chart +### Uninstalling the Chart 🗑️ To uninstall the `node-red` deployment: @@ -67,7 +67,7 @@ helm install node-red node-red/node-red -f values.yaml > **Tip**: You can use the default [values.yaml](values.yaml) -## Monitoring +## Monitoring 🌡️ To enable the node-red prometheus monitoring capability, you need to install the node `node-red-contrib-prometheus-exporter`. For more details see [official documentation](https://flows.nodered.org/node/node-red-contrib-prometheus-exporter) @@ -80,16 +80,26 @@ metrics: enabled: true ``` +## Sidecar 🏎️ + +This Chart supports the handling for loading flows from configmaps/secrets via the [k8s-sidecar](https://github.com/kiwigrid/k8s-sidecar) + +You just need to create a configmap/secret with your `node-red` flow.json and annotate it with the a label and value defined in the chart `sidecar`. +Default values are: `node-red-settings:1`. + +The `k8s-sidecar` will then call the `node-red` api to reload the flows. This will be done via a script. To run this script successfully you need to provide the `username` and `password` +of your admin user. The admin user needs to have the right to use the `node-red` API. + ## Contributing 🤝 ### Contributing via GitHub Feel free to join. Checkout the [contributing guide](CONTRIBUTING.md) -## License +## License ⚖️ Apache License, Version 2.0 {{ template "chart.sourcesSection" . }} -{{ template "chart.maintainersSection" . }} \ No newline at end of file +{{ template "chart.maintainersSection" . }} diff --git a/charts/node-red/scripts/flow_refresh.sh b/charts/node-red/scripts/flow_refresh.sh new file mode 100644 index 00000000..b473fc34 --- /dev/null +++ b/charts/node-red/scripts/flow_refresh.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "node-red flow refresh api" +sleep $SLEEP_TIME_SIDECAR +token=$(curl -X POST -sSk --connect-timeout 30 --retry 50 --retry-delay 10 --data "client_id=node-red-admin&grant_type=password&scope=*&username=$USERNAME&password=$PASSWORD" $URL/auth/token | grep "^{" | jq -r .access_token) +curl -k -X POST --connect-timeout 30 --retry 50 --retry-delay 10 -H "Authorization: Bearer $token" -H "content-type: application/json; charset=utf-8" -H "Node-RED-Deployment-Type: reload" -H "Node-RED-API-Version: v2" --data '{"flows": [{"type": "tab"}]}' $URL/flows diff --git a/charts/node-red/templates/deployment.yaml b/charts/node-red/templates/deployment.yaml index 0db5ab9e..6fea948f 100644 --- a/charts/node-red/templates/deployment.yaml +++ b/charts/node-red/templates/deployment.yaml @@ -54,11 +54,66 @@ spec: {{- end }} {{- end }} containers: - {{- if .Values.sidecars }} - {{- with .Values.sidecars }} - {{- toYaml . | nindent 8 }} - {{- end }} + {{- if .Values.extraSidecars }} + {{- toYaml .Values.extraSidecars | nindent 8 }} {{- end}} + {{- if .Values.sidecar.enabled }} + - env: + {{- if .Values.sidecar.extraEnv }} + {{- toYaml .Values.sidecar.extraEnv | nindent 10 }} + {{- end }} + - name: METHOD + value: {{ .Values.sidecar.env.method }} + - name: SLEEP_TIME_SIDECAR + value: '5s' + - name: LABEL + value: {{ .Values.sidecar.env.label }} + - name: LABEL_VALUE + value: {{ .Values.sidecar.env.label_value | quote}} + - name: FOLDER + value: data + {{- if .Values.sidecar.env.script }} + - name: SCRIPT + value: /app/{{ .Values.sidecar.env.script }} + {{- end }} + - name: URL + value: {{ printf "http://%s.svc.cluster.local:%d" (include "node-red.fullname" .) (.Values.service.port | int) }} + - name: USERNAME + value: {{ required "please set the username for API refresh call" .Values.sidecar.env.username }} + {{- if or .Values.sidecar.env.password .Values.sidecar.env.passwordFromExistingSecret }} + - name: PASSWORD + {{- if .Values.sidecar.env.password }} + value: {{ .Values.sidecar.env.password | quote }} + {{- else if .Values.sidecar.env.passwordFromExistingSecret }} + valueFrom: + secretKeyRef: + key: {{ .Values.sidecar.env.passwordFromExistingSecret.key }} + name: {{ .Values.sidecar.env.passwordFromExistingSecret.name }} + {{- end }} + {{- end }} + image: "{{ .Values.sidecar.image.registry }}/{{ .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }}" + imagePullPolicy: {{ .Values.sidecar.image.pullPolicy }} + name: node-red-1-sidecar + {{- if .Values.sidecar.resources }} + resources: + {{- toYaml .Values.sidecar.resources | nindent 12 }} + {{- end }} + {{- if .Values.sidecar.securityContext }} + securityContext: + {{- toYaml .Values.sidecar.securityContext | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: /data + name: data + - name: flow-refresh-script + mountPath: /app/{{ .Values.sidecar.env.script }} + subPath: {{ .Values.sidecar.env.script }} + {{- if .Values.sidecar.volumeMounts }} + {{- with .Values.sidecar.volumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + {{- end }} - name: {{ .Chart.Name }} {{- if .Values.securityContext }} securityContext: @@ -66,6 +121,7 @@ spec: {{- end }} image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if or .Values.metrics.enabled .Values.env }} env: {{- if .Values.metrics.enabled}} - name: PROMETHEUS_COLLECT_DEFAULT_METRICS @@ -73,8 +129,9 @@ spec: - name: PROMETHEUS_METRICS_PATH value: {{ .Values.metrics.path | default "/metrics" }} {{- end }} - {{- if .Values.env }} - {{- toYaml .Values.env | nindent 10 }} + {{- with .Values.env }} + {{- toYaml .| nindent 10 }} + {{- end }} {{- end }} ports: - name: http @@ -112,6 +169,11 @@ spec: {{- toYaml .Values.resources | nindent 12 }} {{- end }} volumes: + {{- if and .Values.sidecar.enabled .Values.sidecar.volumeMounts }} + - name: flow-refresh-script + configMap: + name: flow-refresh-script + {{- end }} {{- with .Values.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/node-red/templates/sidecar-cm.yaml b/charts/node-red/templates/sidecar-cm.yaml new file mode 100644 index 00000000..75245d9e --- /dev/null +++ b/charts/node-red/templates/sidecar-cm.yaml @@ -0,0 +1,11 @@ +{{- if .Values.sidecar.enabled }} +apiVersion: v1 +data: + flow_refresh.sh: | {{ range $.Files.Lines "scripts/flow_refresh.sh" }} + {{ . }}{{ end }} +kind: ConfigMap +metadata: + name: flow-refresh-script + labels: + {{ .Values.sidecar.env.label}}: {{ .Values.sidecar.env.label_value}} +{{- end }} diff --git a/charts/node-red/tests/deployment_test.yaml b/charts/node-red/tests/deployment_test.yaml new file mode 100644 index 00000000..d2511f8c --- /dev/null +++ b/charts/node-red/tests/deployment_test.yaml @@ -0,0 +1,50 @@ +suite: check sidecar in deployment +templates: + - deployment.yaml +tests: + - it: deployment should render, when sidecar.enabled=true + asserts: + - hasDocuments: + count: 1 + - it: check for envs in sidecar env + values: + - ./values/sidecar_values.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].env[0].value + value: watch + - equal: + path: spec.template.spec.containers[0].env[1].value + value: 5s + - equal: + path: spec.template.spec.containers[0].env[2].value + value: node-red-settings + - equal: + path: spec.template.spec.containers[0].env[3].value + value: "1" + - equal: + path: spec.template.spec.containers[0].env[4].value + value: data + - equal: + path: spec.template.spec.containers[0].env[5].value + value: /app/flow_refresh.sh + - equal: + path: spec.template.spec.containers[0].env[6].value + value: http://RELEASE-NAME-node-red.svc.cluster.local:1880 + - equal: + path: spec.template.spec.containers[0].env[7].value + value: x + - equal: + path: spec.template.spec.containers[0].env[8].value + value: "y" + - it: check volume mounts of for refresh script + values: + - ./values/sidecar_values.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + mountPath: /app/flow_refresh.sh + name: flow-refresh-script + subPath: flow_refresh.sh + diff --git a/charts/node-red/tests/flow_refresh_cm_test.yaml b/charts/node-red/tests/flow_refresh_cm_test.yaml new file mode 100644 index 00000000..1d5c576b --- /dev/null +++ b/charts/node-red/tests/flow_refresh_cm_test.yaml @@ -0,0 +1,28 @@ +suite: configmap check for refresh.sh +templates: + - sidecar-cm.yaml +tests: + - it: config should not render, when sidecar.enabled=false + asserts: + - hasDocuments: + count: 0 + - it: check configmap name is 'flow-refresh-script' + values: + - ./values/sidecar_values.yaml + asserts: + - equal: + path: metadata.name + value: flow-refresh-script + - it: label node-red-flow-refresh-cm-test should have the value '1' + values: + - ./values/sidecar_values.yaml + asserts: + - equal: + path: metadata.labels.node-red-settings + value: 1 + - it: check that data is not empty + values: + - ./values/sidecar_values.yaml + asserts: + - isNotEmpty: + path: data.flow_refresh\.sh diff --git a/charts/node-red/tests/values/sidecar_values.yaml b/charts/node-red/tests/values/sidecar_values.yaml new file mode 100644 index 00000000..ee6656dd --- /dev/null +++ b/charts/node-red/tests/values/sidecar_values.yaml @@ -0,0 +1,19 @@ +sidecar: + enabled: true + env: + method: watch + label: node-red-settings + label_value: "1" + script: flow_refresh.sh + username: "x" + password: "y" + passwordFromExistingSecret: {} + extraEnv: [] + resources: {} + securityContext: {} + image: + registry: quay.io + repository: kiwigrid/k8s-sidecar + tag: 1.15.9 + pullPolicy: IfNotPresent + volumeMounts: [] diff --git a/charts/node-red/values.yaml b/charts/node-red/values.yaml index 1cebd76e..b5b31c49 100644 --- a/charts/node-red/values.yaml +++ b/charts/node-red/values.yaml @@ -80,7 +80,7 @@ service: # -- Kubernetes port where service is exposed port: 1880 -# -- Enable Service-Monitor for node-red +# Enable Service-Monitor for node-red metrics: # -- Deploy metrics service enabled: false @@ -138,7 +138,7 @@ ingress: # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - # -- Ingress accepted hostnames + # Ingress accepted hostnames - host: chart-example.local paths: # -- The base path @@ -210,8 +210,51 @@ settings: {} # configMapName: settings-config -# -- You can configure a sidecar containers to run alongside the node-red pod. default: [] -sidecars: [] +# -- You can configure extra sidecars containers to run alongside the node-red pod. default: [] +extraSidecars: [] # - name: sidecar-example # image: busybox # command: ["/bin/sh", "-c", "echo hello from sidecar"] + +# Sidecar that collect the configmaps with specified label and stores the included files into the given folder +sidecar: + # -- Enable the sidecar + enabled: false + # Env variables to pass to the sidecar + env: + # -- If METHOD is set to LIST, the sidecar will just list config-maps/secrets and exit. With SLEEP it will list all config-maps/secrets, then sleep for SLEEP_TIME seconds. Anything else will continuously watch for changes (see https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes). + method: watch + # -- Label that should be used for filtering + label: node-red-settings + # -- The value for the label you want to filter your resources on. Don't set a value to filter by any value + label_value: "1" + # -- Absolute path to shell script to execute after a configmap got reloaded. + script: flow_refresh.sh + # The username for the API Call, check node-red documentation for more information + username: "" + # -- Password as key value pair + password: "" + # -- Password from existing secret + passwordFromExistingSecret: {} + # -- Name of the secret that contains the password + # name: node-red-api-admin-password + # -- Key of the secret that contains the password + # key: password + # -- Extra Environments for the sidecar + extraEnv: [] + # -- Resources for the sidecar + resources: {} + # -- Security context for the sidecar + securityContext: {} + # Image for the sidecar + image: + # -- The image registry to pull the sidecar from + registry: quay.io + # -- The image repository to pull from + repository: kiwigrid/k8s-sidecar + # -- The image tag to pull, default: ` 1.15.9` + tag: 1.15.9 + # -- The image pull policy, default: `IfNotPresent` + pullPolicy: IfNotPresent + # -- The extra volume mounts for the sidecar + volumeMounts: []