From 7959594510e2b1d2420194177cc5e4c938c94845 Mon Sep 17 00:00:00 2001 From: MergeCheck Date: Fri, 3 Jul 2026 16:29:41 -0300 Subject: [PATCH] fix(langflow): align template standards --- charts/langflow/README.md | 6 +- charts/langflow/templates/NOTES.txt | 79 ++++++++++++++----- charts/langflow/templates/_helpers.tpl | 16 ++++ charts/langflow/templates/deployment.yaml | 2 +- charts/langflow/templates/ingress.yaml | 4 +- charts/langflow/templates/networkpolicy.yaml | 22 ++++++ charts/langflow/templates/validate.yaml | 7 +- charts/langflow/tests/networkpolicy_test.yaml | 36 +++++++++ charts/langflow/tests/templates_test.yaml | 16 ++++ charts/langflow/tests/validation_test.yaml | 15 ++++ charts/langflow/values.schema.json | 14 +++- charts/langflow/values.yaml | 10 +++ 12 files changed, 196 insertions(+), 31 deletions(-) diff --git a/charts/langflow/README.md b/charts/langflow/README.md index 975a9441b..18fc5b30a 100644 --- a/charts/langflow/README.md +++ b/charts/langflow/README.md @@ -1,7 +1,7 @@ # Langflow Helm Chart Langflow is a visual builder for AI workflows, RAG applications, agents, and integrations with model providers and vector databases. -This HelmForge chart deploys the official `docker.io/langflowai/langflow:1.10.0` image with persistent local state by default and explicit +This HelmForge chart deploys the official `docker.io/langflowai/langflow:1.10.1` image with persistent local state by default and explicit production paths for secret management, PostgreSQL-compatible databases, ingress, Gateway API, and horizontal scaling. ## Install @@ -12,6 +12,10 @@ helm install langflow helmforge/langflow ``` The default deployment starts one Langflow pod on port `7860` and persists `/app/langflow`, which contains local configuration and the default SQLite database. +Ingress class rendering is optional. Set `ingress.ingressClassName: ""` to omit `spec.ingressClassName`. +When `networkPolicy.enabled=true`, ingress is restricted to the configured peers. +It also enables egress isolation with built-in DNS and HTTPS allowances, then appends `networkPolicy.extraEgress` for database, provider, or proxy rules. +Set `networkPolicy.dnsEgressPeers` when your cluster DNS pods do not use the default kube-system/kube-dns labels. ## Production Configuration diff --git a/charts/langflow/templates/NOTES.txt b/charts/langflow/templates/NOTES.txt index ea0abdb3c..63c5c23cd 100644 --- a/charts/langflow/templates/NOTES.txt +++ b/charts/langflow/templates/NOTES.txt @@ -1,57 +1,96 @@ {{/* SPDX-License-Identifier: Apache-2.0 */}} Langflow has been deployed. -Access: +1. Access -- Service: http://{{ include "langflow.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }} -- Port-forward: kubectl port-forward svc/{{ include "langflow.fullname" . }} 7860:{{ .Values.service.port }} -n {{ .Release.Namespace }} -- Browser: http://127.0.0.1:7860 +- Service DNS: http://{{ include "langflow.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }} +- Port-forward: + kubectl port-forward svc/{{ include "langflow.fullname" . }} 7860:{{ .Values.service.port }} -n {{ .Release.Namespace }} +- Browser URL: + http://127.0.0.1:7860 -State: +2. Runtime +- Image: {{ include "langflow.image" . }} +- HTTP port: {{ .Values.app.port }} - Config directory: {{ .Values.persistence.mountPath }} + +3. State + {{- if .Values.persistence.enabled }} - Persistence: enabled +- PVC source: {{ default (include "langflow.fullname" .) .Values.persistence.existingClaim }} +- Size: {{ .Values.persistence.size }} +- Access modes: {{ join "," .Values.persistence.accessModes }} {{- else }} -- Persistence: disabled; local SQLite data and config are lost when the pod is deleted +- Persistence: disabled. +- Local SQLite data and local config are lost when the pod is deleted. {{- end }} {{- if or .Values.database.url .Values.database.existingSecret }} -- Database: external via LANGFLOW_DATABASE_URL +- Database: external via LANGFLOW_DATABASE_URL. {{- else }} -- Database: local SQLite in the config directory +- Database: local SQLite in the config directory. {{- end }} -{{- if or .Values.auth.secretKey .Values.auth.existingSecret }} -Authentication: +4. Authentication +{{- if or .Values.auth.secretKey .Values.auth.existingSecret }} - Auth Secret: {{ default (include "langflow.fullname" .) .Values.auth.existingSecret }} - Secret key entry: {{ .Values.auth.secretKeyKey }} - Superuser entry: {{ .Values.auth.superuserKey }} - Superuser password entry: {{ .Values.auth.superuserPasswordKey }} {{- else }} -Authentication: - - No chart-managed Langflow auth Secret is configured. - Set auth.existingSecret before exposing Langflow to users. {{- end }} -{{- if gt (int .Values.replicaCount) 1 }} -Scaling: +5. Scaling - Replicas: {{ .Values.replicaCount }} -- Shared database is configured; verify all replicas can read/write the same flows. +{{- if gt (int .Values.replicaCount) 1 }} +- Shared database is configured; verify all replicas can read and write the same flows. +- If persistence is enabled, use ReadWriteMany storage or an externally managed RWX claim. +{{- else }} +- Single-replica mode can use local SQLite under {{ .Values.persistence.mountPath }}. +{{- end }} + +6. Exposure + +{{- if .Values.ingress.enabled }} +- Ingress is enabled. +- Protect Langflow with authentication before exposing it to users. +{{- else }} +- Ingress is disabled. +{{- end }} +{{- if .Values.gateway.enabled }} +- Gateway API HTTPRoute is enabled. +{{- else }} +- Gateway API HTTPRoute is disabled. +{{- end }} +{{- if .Values.networkPolicy.enabled }} +- NetworkPolicy is enabled for inbound Langflow traffic. +{{- else }} +- NetworkPolicy is disabled. {{- end }} -Troubleshooting: +7. Troubleshooting -- kubectl get pod -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }} -- kubectl logs deploy/{{ include "langflow.fullname" . }} -n {{ .Release.Namespace }} -- kubectl describe pvc {{ include "langflow.fullname" . }} -n {{ .Release.Namespace }} +- Pods: + kubectl get pod -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }} +- Logs: + kubectl logs deploy/{{ include "langflow.fullname" . }} -n {{ .Release.Namespace }} +{{- if .Values.persistence.enabled }} +- PVC: + kubectl describe pvc {{ default (include "langflow.fullname" .) .Values.persistence.existingClaim }} -n {{ .Release.Namespace }} +{{- end }} +- Service endpoints: + kubectl get endpoints {{ include "langflow.fullname" . }} -n {{ .Release.Namespace }} -Resources: +8. Resources - Chart documentation: https://helmforge.dev/docs/charts/langflow - Langflow documentation: https://docs.langflow.org/ +- Langflow deployment docs: https://docs.langflow.org/deployment-kubernetes-prod - HelmForge issues: https://github.com/helmforgedev/charts/issues Happy Helmforging :) diff --git a/charts/langflow/templates/_helpers.tpl b/charts/langflow/templates/_helpers.tpl index 37504bbf0..f2faca025 100644 --- a/charts/langflow/templates/_helpers.tpl +++ b/charts/langflow/templates/_helpers.tpl @@ -16,3 +16,19 @@ app.kubernetes.io/part-of: helmforge {{- end -}} {{- define "langflow.serviceAccountName" -}}{{- if .Values.serviceAccount.create -}}{{- default (include "langflow.fullname" .) .Values.serviceAccount.name -}}{{- else -}}{{- default "default" .Values.serviceAccount.name -}}{{- end -}}{{- end -}} {{- define "langflow.image" -}}{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}{{- end -}} + +{{- define "langflow.validate" -}} +{{- if and (gt (int .Values.replicaCount) 1) (not (or .Values.database.url .Values.database.existingSecret)) -}} +{{- fail "replicaCount > 1 requires database.url or database.existingSecret for shared Langflow state" -}} +{{- end -}} +{{- if and (gt (int .Values.replicaCount) 1) .Values.persistence.enabled (not .Values.persistence.existingClaim) (not (has "ReadWriteMany" .Values.persistence.accessModes)) -}} +{{- fail "replicaCount > 1 with persistence.enabled requires persistence.accessModes to include ReadWriteMany or persistence.enabled=false" -}} +{{- end -}} +{{- $podLabels := .Values.podLabels | default dict -}} +{{- if hasKey $podLabels "app.kubernetes.io/name" -}} +{{- fail "podLabels must not override the selector label app.kubernetes.io/name" -}} +{{- end -}} +{{- if hasKey $podLabels "app.kubernetes.io/instance" -}} +{{- fail "podLabels must not override the selector label app.kubernetes.io/instance" -}} +{{- end -}} +{{- end -}} diff --git a/charts/langflow/templates/deployment.yaml b/charts/langflow/templates/deployment.yaml index 58af55f68..de9ef59bd 100644 --- a/charts/langflow/templates/deployment.yaml +++ b/charts/langflow/templates/deployment.yaml @@ -13,7 +13,7 @@ spec: template: metadata: labels: - {{- include "langflow.labels" . | nindent 8 }} + {{- include "langflow.selectorLabels" . | nindent 8 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/langflow/templates/ingress.yaml b/charts/langflow/templates/ingress.yaml index abf2a1041..d2acba197 100644 --- a/charts/langflow/templates/ingress.yaml +++ b/charts/langflow/templates/ingress.yaml @@ -11,7 +11,9 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} spec: - ingressClassName: {{ .Values.ingress.ingressClassName | quote }} + {{- with .Values.ingress.ingressClassName }} + ingressClassName: {{ . | quote }} + {{- end }} {{- with .Values.ingress.tls }} tls: {{- toYaml . | nindent 4 }} diff --git a/charts/langflow/templates/networkpolicy.yaml b/charts/langflow/templates/networkpolicy.yaml index 082ddfeac..1906277d0 100644 --- a/charts/langflow/templates/networkpolicy.yaml +++ b/charts/langflow/templates/networkpolicy.yaml @@ -1,5 +1,7 @@ {{/* SPDX-License-Identifier: Apache-2.0 */}} {{- if .Values.networkPolicy.enabled }} +{{- $extraEgress := .Values.networkPolicy.extraEgress | default list }} +{{- $dnsEgressPeers := .Values.networkPolicy.dnsEgressPeers | default list }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -12,6 +14,7 @@ spec: {{- include "langflow.selectorLabels" . | nindent 6 }} policyTypes: - Ingress + - Egress ingress: - from: {{- if .Values.networkPolicy.ingressFrom }} @@ -22,4 +25,23 @@ spec: ports: - protocol: TCP port: http + egress: + - to: + {{- toYaml $dnsEgressPeers | nindent 8 }} + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 0.0.0.0/0 + - ipBlock: + cidr: ::/0 + ports: + - protocol: TCP + port: 443 + {{- with $extraEgress }} + {{- toYaml . | nindent 4 }} + {{- end }} {{- end }} diff --git a/charts/langflow/templates/validate.yaml b/charts/langflow/templates/validate.yaml index 6b82b618a..b1b817440 100644 --- a/charts/langflow/templates/validate.yaml +++ b/charts/langflow/templates/validate.yaml @@ -1,7 +1,2 @@ {{/* SPDX-License-Identifier: Apache-2.0 */}} -{{- if and (gt (int .Values.replicaCount) 1) (not (or .Values.database.url .Values.database.existingSecret)) -}} -{{- fail "replicaCount > 1 requires database.url or database.existingSecret for shared Langflow state" -}} -{{- end -}} -{{- if and (gt (int .Values.replicaCount) 1) .Values.persistence.enabled (not (has "ReadWriteMany" .Values.persistence.accessModes)) -}} -{{- fail "replicaCount > 1 with persistence.enabled requires persistence.accessModes to include ReadWriteMany or persistence.enabled=false" -}} -{{- end -}} +{{- include "langflow.validate" . -}} diff --git a/charts/langflow/tests/networkpolicy_test.yaml b/charts/langflow/tests/networkpolicy_test.yaml index 8845d5e80..9c648ade8 100644 --- a/charts/langflow/tests/networkpolicy_test.yaml +++ b/charts/langflow/tests/networkpolicy_test.yaml @@ -9,8 +9,44 @@ tests: asserts: - isKind: of: NetworkPolicy + - contains: + path: spec.policyTypes + content: Egress - contains: path: spec.ingress[0].ports content: protocol: TCP port: http + - equal: + path: spec.egress[0].to[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"] + value: kube-system + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels["k8s-app"] + value: kube-dns + - equal: + path: spec.egress[1].ports[0].port + value: 443 + - it: appends extra egress rules after default DNS and HTTPS + set: + networkPolicy: + enabled: true + extraEgress: + - to: + - ipBlock: + cidr: 10.0.0.0/8 + ports: + - protocol: TCP + port: 5432 + asserts: + - contains: + path: spec.policyTypes + content: Egress + - equal: + path: spec.egress[0].ports[0].port + value: 53 + - equal: + path: spec.egress[1].ports[0].port + value: 443 + - equal: + path: spec.egress[2].ports[0].port + value: 5432 diff --git a/charts/langflow/tests/templates_test.yaml b/charts/langflow/tests/templates_test.yaml index 8bf1acf71..76f9270fc 100644 --- a/charts/langflow/tests/templates_test.yaml +++ b/charts/langflow/tests/templates_test.yaml @@ -15,6 +15,11 @@ tests: - equal: path: spec.template.spec.containers[0].image value: docker.io/langflowai/langflow:1.10.1 + - equal: + path: spec.template.metadata.labels["app.kubernetes.io/name"] + value: langflow + - notExists: + path: spec.template.metadata.labels["helm.sh/chart"] - it: renders service template: templates/service.yaml asserts: @@ -42,6 +47,17 @@ tests: - equal: path: metadata.annotations["cert-manager.io/cluster-issuer"] value: letsencrypt + - it: omits empty ingress class name + template: templates/ingress.yaml + set: + ingress.enabled: true + ingress.ingressClassName: "" + ingress.hosts[0].host: langflow.example.com + ingress.hosts[0].paths[0].path: / + ingress.hosts[0].paths[0].pathType: Prefix + asserts: + - notExists: + path: spec.ingressClassName - it: renders HTTPRoute hostnames template: templates/httproute.yaml set: diff --git a/charts/langflow/tests/validation_test.yaml b/charts/langflow/tests/validation_test.yaml index b8e00e9a6..0ee1685f2 100644 --- a/charts/langflow/tests/validation_test.yaml +++ b/charts/langflow/tests/validation_test.yaml @@ -17,3 +17,18 @@ tests: asserts: - failedTemplate: errorMessage: "replicaCount > 1 with persistence.enabled requires persistence.accessModes to include ReadWriteMany or persistence.enabled=false" + - it: allows scaling with an existing persistence claim + set: + replicaCount: 2 + database.existingSecret: langflow-database + persistence.existingClaim: langflow-data-rwx + persistence.accessModes[0]: ReadWriteOnce + asserts: + - notFailedTemplate: {} + - it: fails when podLabels override selector labels + set: + podLabels: + app.kubernetes.io/name: custom + asserts: + - failedTemplate: + errorMessage: "podLabels must not override the selector label app.kubernetes.io/name" diff --git a/charts/langflow/values.schema.json b/charts/langflow/values.schema.json index 79699a03e..eef5d3b7d 100644 --- a/charts/langflow/values.schema.json +++ b/charts/langflow/values.schema.json @@ -12,7 +12,7 @@ "type": "object", "properties": { "repository": { "type": "string", "default": "docker.io/langflowai/langflow" }, - "tag": { "type": "string", "default": "1.10.0" }, + "tag": { "type": "string", "default": "1.10.1" }, "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"], "default": "IfNotPresent" } } }, @@ -76,7 +76,17 @@ "type": "object", "properties": { "enabled": { "type": "boolean", "default": false }, - "ingressFrom": { "type": "array", "items": { "type": "object" } } + "ingressFrom": { "type": "array", "items": { "type": "object" } }, + "dnsEgressPeers": { + "type": "array", + "description": "DNS peers allowed by the built-in egress allowance", + "items": { "type": "object" } + }, + "extraEgress": { + "type": "array", + "description": "Additional egress rules appended after the built-in DNS and HTTPS allowances", + "items": { "type": "object" } + } } }, "probes": { "$ref": "#/definitions/probes" }, diff --git a/charts/langflow/values.yaml b/charts/langflow/values.yaml index ce869befc..01406e5b2 100644 --- a/charts/langflow/values.yaml +++ b/charts/langflow/values.yaml @@ -136,6 +136,16 @@ networkPolicy: enabled: false # -- Ingress peers allowed to reach Langflow. Empty allows all namespaces when policy is enabled. ingressFrom: [] + # -- DNS peers allowed when egress isolation is enabled. + dnsEgressPeers: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + # -- Additional egress rules appended after the built-in DNS and HTTPS allowances. + extraEgress: [] probes: # -- Startup probe settings.