diff --git a/EXAMPLES.md b/EXAMPLES.md index 3d6824ae4..3f0e91af8 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -623,8 +623,91 @@ extraObjects: To develop or test plugins without pushing them to a public registry, you can load plugin source code directly from your local filesystem. ->[!NOTE] -> The ``hostPath`` must point to a directory containing the plugin source code and a valid ``go.mod`` file. The ``moduleName`` must match the module name specified in the ``go.mod`` file. +>[!WARNING] +> The legacy `hostPath` configuration at the `experimental.localPlugins` level is deprecated. Please use the new structured `experimental.localPlugins..type` configuration for better organization and future features. + +### Legacy Configuration + +>[!WARNING] +> This legacy `hostPath` configuration is deprecated and will be removed in the next major version. Please migrate to the structured `type` configuration below. + +```yaml +experimental: + localPlugins: + legacy-demo: + moduleName: github.com/traefik/legacydemo + mountPath: /plugins-local/src/github.com/traefik/legacydemo + hostPath: /path/to/plugin-source # ⚠️ Deprecated - use type: hostPath instead +``` + +## Structured Local Plugins + +The `localPlugins` configuration supports a structured `experimental.localPlugins..type` approach that provides better organization, security, and flexibility: + +### Using Inline Plugin + +> [!NOTE] +> Can be used with small or medium plugins + +For testing or general use, embed plugin source directly in values.yaml using the secure `inlinePlugin` type: + +```yaml +experimental: + localPlugins: + helloworld-plugin: + moduleName: github.com/example/helloworldplugin + mountPath: /plugins-local/src/github.com/example/helloworldplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/helloworldplugin + + go 1.23 + .traefik.yml: | + displayName: Hello World Plugin + type: middleware + + import: github.com/example/helloworldplugin + + summary: | + This is a simple plugin that prints "Hello, World!" to the response. + + testData: + message: "Hello, World!" + main.go: | + package helloworldplugin + + import ( + "context" + "net/http" + ) + + type Config struct{} + + func CreateConfig() *Config { + return &Config{} + } + + type HelloWorld struct { + next http.Handler + } + + func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { + return &HelloWorld{next: next}, nil + } + + func (h *HelloWorld) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + rw.Write([]byte("Hello, World!")) + h.next.ServeHTTP(rw, req) + } +``` + +> **Advantages**: Secure (no host filesystem access), portable, version controlled with Helm values, supports up to 1MB of plugin code. + +### Using Host Path Plugin + +>[!WARNING] +> The `hostPath` type should be avoided for security reasons and requires additional work to pull plugins from repositories or blob storage. Consider using `inlinePlugin` or `localPath` instead. ```yaml experimental: @@ -632,11 +715,46 @@ experimental: local-demo: moduleName: github.com/traefik/localplugindemo mountPath: /plugins-local/src/github.com/traefik/localplugindemo + type: hostPath hostPath: /path/to/plugin-source ``` ->[!IMPORTANT] -> When using ``hostPath`` volumes, the plugin source code must be available on every node where Traefik pods might be scheduled. +### Using Local Path Plugin (Advanced) + +>[!NOTE] +> The `localPath` type leverages the existing `additionalVolumes` mechanism for maximum flexibility. This supports PVC, CSI drivers (s3-csi-driver, FUSE), and other volume types. + +```yaml +# Define the volume in additionalVolumes first +deployment: + additionalVolumes: + - name: plugin-storage + persistentVolumeClaim: + claimName: plugin-storage-pvc + # Or use CSI driver for S3/blob storage: + # - name: s3-plugin-storage + # csi: + # driver: s3.csi.aws.com + # volumeAttributes: + # bucketName: my-plugin-bucket + +# Then reference it in localPlugins +experimental: + localPlugins: + s3-plugin: + moduleName: github.com/example/s3plugin + mountPath: /plugins-local/src/github.com/example/s3plugin + type: localPath + volumeName: plugin-storage # Must match additionalVolumes name + subPath: plugins/s3plugin # Optional subpath within volume +``` + +> **Advantages**: +> +> - **Flexible**: Supports any Kubernetes volume type (PVC, CSI, NFS, etc.) +> - **Secure**: Works with CSI drivers for cloud storage (S3, Azure Blob, GCS) +> - **Scalable**: Centralized plugin storage, no per-node requirements +> - **Consistent**: Uses existing Helm chart patterns (`additionalVolumes`) ## Using Traefik-Hub with private plugin registries @@ -778,8 +896,8 @@ podSecurityContext: Setup: -* cert-manager installed in `cert-manager` namespace -* A cloudflare account on a DNS Zone +- cert-manager installed in `cert-manager` namespace +- A cloudflare account on a DNS Zone **Step 1**: Create `Secret` and `Issuer` needed by `cert-manager` with your API Token. See [cert-manager documentation](https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/) diff --git a/traefik/templates/NOTES.txt b/traefik/templates/NOTES.txt index 2a483852b..6e6458b00 100644 --- a/traefik/templates/NOTES.txt +++ b/traefik/templates/NOTES.txt @@ -82,6 +82,30 @@ for more info. 🚨 {{- end -}} {{- end -}} +{{/* Warn about deprecated localPlugins */}} + {{- if include "traefik.hasDeprecatedLocalPlugins" . }} + {{- printf "\n" -}} +⚠️ DEPRECATION WARNING: You are using the deprecated legacy 'hostPath' configuration. +Please migrate to the new structured 'type.hostPathPlugin' configuration within localPlugins. +The legacy root-level hostPath configuration will be removed in the next major version. + +Migration example: + experimental: + localPlugins: + your-plugin: + moduleName: github.com/example/yourplugin + mountPath: /plugins-local/src/github.com/example/yourplugin + # Choose one of the following types: + type: inlinePlugin # Recommended for small/medium plugins: secure ConfigMap-based + source: # Required for inlinePlugin + # your plugin files here + # type: hostPath # Use with caution for security reasons + # hostPath: /path/to/plugin + # type: localPath # Advanced: Uses additionalVolumes, can be used with PVC, CSI drivers (s3-csi-driver, FUSE), etc. + # volumeName: plugin-storage + {{- printf "\n" -}} + {{- end -}} + {{/* Warn about missing secret when enabling managed certificate with Hub admission controller */}} {{- if and .Values.hub.token .Values.hub.apimanagement.enabled .Values.hub.apimanagement.admission.selfManagedCertificate }} diff --git a/traefik/templates/_helpers.tpl b/traefik/templates/_helpers.tpl index 6b9aeddff..7a5c8154a 100644 --- a/traefik/templates/_helpers.tpl +++ b/traefik/templates/_helpers.tpl @@ -266,6 +266,10 @@ Hash: {{ sha1sum ($cert.Cert | b64enc) }} {{- end -}} {{- end -}} +{{- define "traefik.localPluginCmName" -}} + {{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }} +{{- end -}} + {{- define "traefik.hasPluginsVolume" -}} {{- $found := false -}} {{- range . -}} @@ -276,6 +280,98 @@ Hash: {{ sha1sum ($cert.Cert | b64enc) }} {{- $found -}} {{- end -}} +{{/* +Validate localPlugin configuration and determine plugin type +Returns: hostPath, inline, or localPath +*/}} +{{- define "traefik.getLocalPluginType" -}} + {{- $plugin := .plugin -}} + {{- if $plugin.type -}} + {{- if eq $plugin.type "hostPath" -}} + {{- printf "hostPath" -}} + {{- else if eq $plugin.type "inlinePlugin" -}} + {{- printf "inlinePlugin" -}} + {{- else if eq $plugin.type "localPath" -}} + {{- printf "localPath" -}} + {{- else -}} + {{- fail (printf "ERROR: localPlugin %s has invalid type configuration. Must specify one of: hostPath, inlinePlugin, localPath" .pluginName) -}} + {{- end -}} + {{- else if $plugin.hostPath -}} + {{- printf "hostPath" -}} + {{- else -}} + {{- fail (printf "ERROR: localPlugin %s must specify either legacy hostPath configuration or new type configuration!" .pluginName) -}} + {{- end -}} +{{- end -}} + +{{/* +Get hostPath for a plugin (handles both old and new structure) +*/}} +{{- define "traefik.getLocalPluginHostPath" -}} + {{- $plugin := .plugin -}} + {{- if $plugin.type -}} + {{- if eq $plugin.type "hostPath" -}} + {{- $plugin.hostPath -}} + {{- end -}} + {{- else -}} + {{- $plugin.hostPath -}} + {{- end -}} +{{- end -}} + +{{/* +Get inline plugin files (new structure only) +*/}} +{{- define "traefik.getLocalPluginInlineFiles" -}} + {{- $plugin := .plugin -}} + {{- if eq $plugin.type "inlinePlugin" -}} + {{- required (printf "ERROR: localPlugin %s with type inlinePlugin must have a source field!" .pluginName) $plugin.source | toYaml -}} + {{- end -}} +{{- end -}} + +{{/* +Get localPath plugin configuration (new structure only) +*/}} +{{- define "traefik.getLocalPluginLocalPath" -}} + {{- $plugin := .plugin -}} + {{- if eq $plugin.type "localPath" -}} + {{- $localPathConfig := dict -}} + {{- range $key, $value := $plugin -}} + {{- if and (ne $key "type") (ne $key "moduleName") (ne $key "mountPath") -}} + {{- $_ := set $localPathConfig $key $value -}} + {{- end -}} + {{- end -}} + {{- toYaml $localPathConfig -}} + {{- end -}} +{{- end -}} + +{{/* +Check if a volume name exists in additionalVolumes +*/}} +{{- define "traefik.volumeExistsInAdditionalVolumes" -}} + {{- $volumeName := .volumeName -}} + {{- $additionalVolumes := .additionalVolumes -}} + {{- $found := false -}} + {{- range $additionalVolumes -}} + {{- if eq .name $volumeName -}} + {{- $found = true -}} + {{- end -}} + {{- end -}} + {{- $found -}} +{{- end -}} + +{{/* +Check if using old localPlugin hostPath structure (for deprecation warning) +*/}} +{{- define "traefik.hasDeprecatedLocalPlugins" -}} + {{- if .Values.experimental.localPlugins -}} + {{- range $pluginName, $plugin := .Values.experimental.localPlugins -}} + {{- if $plugin.hostPath -}} + {{- printf "true" -}} + {{- break -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + {{- define "list.difference" -}} {{- $a := .a }} {{- $b := .b }} diff --git a/traefik/templates/_podtemplate.tpl b/traefik/templates/_podtemplate.tpl index 0c6c70a7a..165791e42 100644 --- a/traefik/templates/_podtemplate.tpl +++ b/traefik/templates/_podtemplate.tpl @@ -176,9 +176,23 @@ {{- tpl (toYaml .Values.additionalVolumeMounts) . | nindent 10 }} {{- end }} {{- range $localPluginName, $localPlugin := .Values.experimental.localPlugins }} + {{- $pluginType := include "traefik.getLocalPluginType" (dict "plugin" $localPlugin "pluginName" $localPluginName) }} + {{- if eq $pluginType "localPath" }} + {{- $localPathConfig := include "traefik.getLocalPluginLocalPath" (dict "plugin" $localPlugin) | fromYaml }} + - name: {{ $localPathConfig.volumeName }} + mountPath: {{ $localPlugin.mountPath | quote }} + {{- if $localPathConfig.subPath }} + subPath: {{ $localPathConfig.subPath }} + {{- end }} + {{- else }} - name: {{ $localPluginName | replace "." "-" }} mountPath: {{ $localPlugin.mountPath | quote }} + {{- if eq $pluginType "inlinePlugin" }} + readOnly: true + {{- end }} + {{- end }} {{- end }} + args: {{- with .Values.global }} {{- if not .checkNewVersion }} @@ -445,6 +459,7 @@ - "--experimental.localPlugins.{{ $localPluginName }}.settings.useUnsafe=true" {{- end }} {{- end }} + {{- if and (semverCompare ">=v3.3.0-0" $version) (.Values.experimental.abortOnPluginFailure)}} - "--experimental.abortonpluginfailure={{ .Values.experimental.abortOnPluginFailure }}" {{- end }} @@ -927,9 +942,24 @@ {{- toYaml .Values.deployment.additionalVolumes | nindent 8 }} {{- end }} {{- range $localPluginName, $localPlugin := .Values.experimental.localPlugins }} + {{- $pluginType := include "traefik.getLocalPluginType" (dict "plugin" $localPlugin "pluginName" $localPluginName) }} + {{- if ne $pluginType "localPath" }} - name: {{ $localPluginName | replace "." "-" }} + {{- if eq $pluginType "hostPath" }} + {{- $hostPath := include "traefik.getLocalPluginHostPath" (dict "plugin" $localPlugin) }} hostPath: - path: {{ $localPlugin.hostPath | quote }} + path: {{ $hostPath | quote }} + {{- else if eq $pluginType "inlinePlugin" }} + configMap: + name: {{ include "traefik.localPluginCmName" (dict "context" $ "pluginName" $localPluginName) }} + {{- end }} + {{- else }} + {{- $localPathConfig := include "traefik.getLocalPluginLocalPath" (dict "plugin" $localPlugin) | fromYaml }} + {{- $volumeExists := include "traefik.volumeExistsInAdditionalVolumes" (dict "volumeName" $localPathConfig.volumeName "additionalVolumes" $.Values.deployment.additionalVolumes) }} + {{- if ne $volumeExists "true" }} + {{- fail (printf "ERROR: localPlugin %s references volume '%s' which is not found in deployment.additionalVolumes!" $localPluginName $localPathConfig.volumeName) }} + {{- end }} + {{- end }} {{- end }} {{- if and (gt (len .Values.experimental.plugins) 0) (ne (include "traefik.hasPluginsVolume" .Values.deployment.additionalVolumes) "true") }} - name: plugins diff --git a/traefik/templates/local-plugins-cm.yaml b/traefik/templates/local-plugins-cm.yaml new file mode 100644 index 000000000..c47d8cac1 --- /dev/null +++ b/traefik/templates/local-plugins-cm.yaml @@ -0,0 +1,21 @@ +{{- if .Values.experimental.localPlugins }} + {{- range $localPluginName, $localPlugin := .Values.experimental.localPlugins }} + {{- $pluginType := include "traefik.getLocalPluginType" (dict "plugin" $localPlugin "pluginName" $localPluginName) }} + {{- if eq $pluginType "inlinePlugin" }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "traefik.localPluginCmName" (dict "context" $ "pluginName" $localPluginName) }} + namespace: {{ template "traefik.namespace" $ }} + labels: + {{- include "traefik.labels" $ | nindent 4 }} +data: + {{- $inlineFiles := include "traefik.getLocalPluginInlineFiles" (dict "plugin" $localPlugin "pluginName" $localPluginName) | fromYaml }} + {{- range $fileName, $fileContent := $inlineFiles }} + {{ $fileName }}: | + {{- $fileContent | nindent 4 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/traefik/tests/local-plugins-configmap_test.yaml b/traefik/tests/local-plugins-configmap_test.yaml new file mode 100644 index 000000000..e71933843 --- /dev/null +++ b/traefik/tests/local-plugins-configmap_test.yaml @@ -0,0 +1,396 @@ +suite: Local Plugins ConfigMap Support +templates: + - deployment.yaml + - daemonset.yaml + - local-plugins-cm.yaml +tests: + # Test ConfigMap generation from inlinePlugin + - it: should create ConfigMap for inlinePlugin + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + test-plugin: + moduleName: github.com/example/testplugin + mountPath: /plugins-local/src/github.com/example/testplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/testplugin + go 1.23 + .traefik.yml: | + displayName: Test Plugin + type: middleware + import: github.com/example/testplugin + main.go: | + package main + // Test plugin code + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: RELEASE-NAME-traefik-local-plugin-test-plugin + - equal: + path: data["go.mod"] + value: | + module github.com/example/testplugin + go 1.23 + - equal: + path: data[".traefik.yml"] + value: | + displayName: Test Plugin + type: middleware + import: github.com/example/testplugin + - equal: + path: data["main.go"] + value: | + package main + // Test plugin code + + # Test multiple ConfigMaps for multiple plugins + - it: should create multiple ConfigMaps for multiple inlinePlugins + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + plugin-one: + moduleName: github.com/example/pluginone + mountPath: /plugins-local/src/github.com/example/pluginone + type: inlinePlugin + source: + go.mod: | + module github.com/example/pluginone + main.go: | + package pluginone + plugin-two: + moduleName: github.com/example/plugintwo + mountPath: /plugins-local/src/github.com/example/plugintwo + type: inlinePlugin + source: + go.mod: | + module github.com/example/plugintwo + main.go: | + package plugintwo + asserts: + - hasDocuments: + count: 2 + - equal: + path: metadata.name + value: RELEASE-NAME-traefik-local-plugin-plugin-one + documentIndex: 0 + - equal: + path: metadata.name + value: RELEASE-NAME-traefik-local-plugin-plugin-two + documentIndex: 1 + + # Test no ConfigMap for hostPath plugins + - it: should not create ConfigMap for hostPath plugins + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + host-plugin: + moduleName: github.com/example/hostplugin + mountPath: /plugins-local/src/github.com/example/hostplugin + type: hostPath + hostPath: /path/to/plugin + asserts: + - hasDocuments: + count: 0 + + # Test mixed hostPath and inlinePlugin + - it: should create ConfigMap only for inlinePlugin when mixed with hostPath + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + host-plugin: + moduleName: github.com/example/hostplugin + mountPath: /plugins-local/src/github.com/example/hostplugin + type: hostPath + hostPath: /path/to/plugin + inline-plugin: + moduleName: github.com/example/inlineplugin + mountPath: /plugins-local/src/github.com/example/inlineplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/inlineplugin + main.go: | + package inlineplugin + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: RELEASE-NAME-traefik-local-plugin-inline-plugin + + # Test deployment volume mounting for inlinePlugin + - it: should create ConfigMap volume for inlinePlugin in deployment + template: deployment.yaml + set: + experimental: + localPlugins: + test-plugin: + moduleName: github.com/example/testplugin + mountPath: /plugins-local/src/github.com/example/testplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/testplugin + main.go: | + package testplugin + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: test-plugin + mountPath: "/plugins-local/src/github.com/example/testplugin" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: test-plugin + configMap: + name: RELEASE-NAME-traefik-local-plugin-test-plugin + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.test-plugin.moduleName=github.com/example/testplugin" + + # Test backward compatibility with hostPath + - it: should create hostPath volume for hostPath plugins in deployment + template: deployment.yaml + set: + experimental: + localPlugins: + host-plugin: + moduleName: github.com/example/hostplugin + mountPath: /plugins-local/src/github.com/example/hostplugin + type: hostPath + hostPath: /path/to/plugin + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: host-plugin + mountPath: "/plugins-local/src/github.com/example/hostplugin" + - contains: + path: spec.template.spec.volumes + content: + name: host-plugin + hostPath: + path: "/path/to/plugin" + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.host-plugin.moduleName=github.com/example/hostplugin" + + # Test mixed scenarios in deployment + - it: should handle mixed hostPath and inlinePlugin volumes in deployment + template: deployment.yaml + set: + experimental: + localPlugins: + host-plugin: + moduleName: github.com/example/hostplugin + mountPath: /plugins-local/src/github.com/example/hostplugin + type: hostPath + hostPath: /path/to/plugin + inline-plugin: + moduleName: github.com/example/inlineplugin + mountPath: /plugins-local/src/github.com/example/inlineplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/inlineplugin + main.go: | + package inlineplugin + asserts: + # HostPath volume + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: host-plugin + mountPath: "/plugins-local/src/github.com/example/hostplugin" + - contains: + path: spec.template.spec.volumes + content: + name: host-plugin + hostPath: + path: "/path/to/plugin" + # Inline plugin volume + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: inline-plugin + mountPath: "/plugins-local/src/github.com/example/inlineplugin" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: inline-plugin + configMap: + name: RELEASE-NAME-traefik-local-plugin-inline-plugin + # Args + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.host-plugin.moduleName=github.com/example/hostplugin" + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.inline-plugin.moduleName=github.com/example/inlineplugin" + + # Test daemonset volume mounting for inlinePlugin + - it: should create ConfigMap volume for inlinePlugin in daemonset + template: daemonset.yaml + set: + deployment: + enabled: true + kind: DaemonSet + experimental: + localPlugins: + test-plugin: + moduleName: github.com/example/testplugin + mountPath: /plugins-local/src/github.com/example/testplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/testplugin + main.go: | + package testplugin + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: test-plugin + mountPath: "/plugins-local/src/github.com/example/testplugin" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: test-plugin + configMap: + name: RELEASE-NAME-traefik-local-plugin-test-plugin + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.test-plugin.moduleName=github.com/example/testplugin" + + # Test mixed scenarios in daemonset + - it: should handle mixed hostPath and inlinePlugin volumes in daemonset + template: daemonset.yaml + set: + deployment: + enabled: true + kind: DaemonSet + experimental: + localPlugins: + host-plugin: + moduleName: github.com/example/hostplugin + mountPath: /plugins-local/src/github.com/example/hostplugin + type: hostPath + hostPath: /path/to/plugin + inline-plugin: + moduleName: github.com/example/inlineplugin + mountPath: /plugins-local/src/github.com/example/inlineplugin + type: inlinePlugin + source: + go.mod: | + module github.com/example/inlineplugin + main.go: | + package inlineplugin + asserts: + # HostPath volume + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: host-plugin + mountPath: "/plugins-local/src/github.com/example/hostplugin" + - contains: + path: spec.template.spec.volumes + content: + name: host-plugin + hostPath: + path: "/path/to/plugin" + # Inline plugin volume + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: inline-plugin + mountPath: "/plugins-local/src/github.com/example/inlineplugin" + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: inline-plugin + configMap: + name: RELEASE-NAME-traefik-local-plugin-inline-plugin + # Args + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.host-plugin.moduleName=github.com/example/hostplugin" + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.inline-plugin.moduleName=github.com/example/inlineplugin" + + # Test error handling for missing moduleName + - it: should fail when inlinePlugin is missing moduleName + template: deployment.yaml + set: + experimental: + localPlugins: + invalid-plugin: + mountPath: /plugins-local/src/github.com/example/invalidplugin + type: inlinePlugin + source: + main.go: | + package main + asserts: + - failedTemplate: + errorMessage: "ERROR: local plugin invalid-plugin is missing moduleName !" + + # Test error handling for missing type configuration + - it: should fail when plugin has neither hostPath nor inlinePlugin + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + invalid-plugin: + moduleName: github.com/example/invalidplugin + mountPath: /plugins-local/src/github.com/example/invalidplugin + asserts: + - failedTemplate: + errorMessage: "ERROR: localPlugin invalid-plugin must specify either legacy hostPath configuration or new type configuration!" + + # Test error handling for missing type configuration in daemonset + - it: should fail when daemonset plugin has neither hostPath nor inlinePlugin + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + invalid-plugin: + moduleName: github.com/example/invalidplugin + mountPath: /plugins-local/src/github.com/example/invalidplugin + asserts: + - failedTemplate: + errorMessage: "ERROR: localPlugin invalid-plugin must specify either legacy hostPath configuration or new type configuration!" + + # Test ConfigMap naming with dots in plugin names + - it: should replace dots in plugin names for ConfigMap and volume names + template: local-plugins-cm.yaml + set: + experimental: + localPlugins: + plugin.with.dots: + moduleName: github.com/example/pluginwithdots + mountPath: /plugins-local/src/github.com/example/pluginwithdots + type: inlinePlugin + source: + main.go: | + package main + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: RELEASE-NAME-traefik-local-plugin-plugin-with-dots diff --git a/traefik/tests/traefik-config_test.yaml b/traefik/tests/traefik-config_test.yaml index 92b3d9c02..b0aec51f5 100644 --- a/traefik/tests/traefik-config_test.yaml +++ b/traefik/tests/traefik-config_test.yaml @@ -444,6 +444,110 @@ tests: - failedTemplate: errorMessage: "ERROR: local plugin local-demo is missing moduleName !" + - it: should create correct volumes, mounts and args for new structured localPlugins and legacy hostPath + set: + experimental: + localPlugins: + # Legacy hostPath (backward compatibility) + legacy-host: + moduleName: github.com/traefik/legacyhost + mountPath: /plugins-local/src/github.com/traefik/legacyhost + hostPath: /plugins-legacy + # New structured plugins + inline-demo: + moduleName: github.com/traefik/inlinedemo + mountPath: /plugins-local/src/github.com/traefik/inlinedemo + type: inlinePlugin + source: + main.go: "package main" + go.mod: | + module github.com/traefik/inlinedemo + go 1.23 + .traefik.yml: | + displayName: Inline Demo + type: middleware + import: github.com/traefik/inlinedemo + summary: | + This is a simple plugin that prints "Hello, World!" to the response. + testData: + message: "Hello, World!" + + host-demo: + moduleName: github.com/traefik/hostdemo + mountPath: /plugins-local/src/github.com/traefik/hostdemo + type: hostPath + hostPath: /plugins-local/src/github.com/traefik/hostdemo + pvc-demo: + moduleName: github.com/traefik/pvcdemo + mountPath: /plugins-local/src/github.com/traefik/pvcdemo + type: localPath + volumeName: plugin-storage + subPath: plugins/pvcdemo + deployment: + additionalVolumes: + - name: plugin-storage + persistentVolumeClaim: + claimName: plugin-storage-pvc + asserts: + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.legacy-host.moduleName=github.com/traefik/legacyhost" + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.inline-demo.moduleName=github.com/traefik/inlinedemo" + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.host-demo.moduleName=github.com/traefik/hostdemo" + - contains: + path: spec.template.spec.containers[0].args + content: "--experimental.localPlugins.pvc-demo.moduleName=github.com/traefik/pvcdemo" + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: legacy-host + mountPath: "/plugins-local/src/github.com/traefik/legacyhost" + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: inline-demo + mountPath: "/plugins-local/src/github.com/traefik/inlinedemo" + readOnly: true + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: host-demo + mountPath: "/plugins-local/src/github.com/traefik/hostdemo" + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: plugin-storage + mountPath: "/plugins-local/src/github.com/traefik/pvcdemo" + subPath: plugins/pvcdemo + - contains: + path: spec.template.spec.volumes + content: + name: legacy-host + hostPath: + path: "/plugins-legacy" + - contains: + path: spec.template.spec.volumes + content: + name: inline-demo + configMap: + name: RELEASE-NAME-traefik-local-plugin-inline-demo + - contains: + path: spec.template.spec.volumes + content: + name: host-demo + hostPath: + path: "/plugins-local/src/github.com/traefik/hostdemo" + - contains: + path: spec.template.spec.volumes + content: + name: plugin-storage + persistentVolumeClaim: + claimName: plugin-storage-pvc + - it: should allow useUnsafe option for plugins and localPlugins set: experimental: @@ -456,6 +560,8 @@ tests: localPlugins: local-demo: moduleName: github.com/traefik/plugindemo + mountPath: /plugins-local/src/github.com/traefik/plugindemo + hostPath: /plugins-local/src/github.com/traefik/plugindemo settings: useUnsafe: true asserts: @@ -474,6 +580,17 @@ tests: - contains: path: spec.template.spec.containers[0].args content: "--experimental.localPlugins.local-demo.settings.useUnsafe=true" + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: local-demo + mountPath: "/plugins-local/src/github.com/traefik/plugindemo" + - contains: + path: spec.template.spec.volumes + content: + name: local-demo + hostPath: + path: "/plugins-local/src/github.com/traefik/plugindemo" - it: should have tls configuration args when specified in values.yaml for port set: