-
Notifications
You must be signed in to change notification settings - Fork 799
feat(plugins): support ConfigMap inline local plugins alongside hostPath #1492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat(plugins): support ConfigMap inline local plugins alongside hostPath #1492
Conversation
…longside hostPath
mloiseleur
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR looks technically correct.
I'll need review of other chart maintainers, just to be sure I'm not missing anything here.
…ame:cilasbeltrame/traefik-helm-chart into feat/support-cm-local-plugins
jnoordsij
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this, I think this is a neat thing to add, especially for people employing small plugins!
From what I can see this is all technically sound, just some coding style questions.
| {{- toYaml .Values.deployment.additionalVolumes | nindent 8 }} | ||
| {{- end }} | ||
| {{- $root := . }} | ||
| {{- range $localPluginName, $localPlugin := .Values.experimental.localPlugins }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the ordering of volumes a bit weird looking at it now; probably the plugin-related volumes, i.e. this range and the /plugins-storage mount, should be grouped. However that is probably more of a follow-up thing to clean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Later on, I'm happy to work on this in a separate PR.
traefik/templates/_podtemplate.tpl
Outdated
| {{- if $localPlugin.hostPath }} | ||
| hostPath: | ||
| path: {{ $localPlugin.hostPath | quote }} | ||
| {{- else if $localPlugin.inlinePlugin }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of values, I find it to be slightly weird that now basically it would be allowed to set both hostPath and inlinePlugin, which will (silently) result in just using the former.
However, I think it's probably still fine, as the alternatives I can come up with are not much better:
- add a required
typevalue to localPlugin = bad because it's a breaking change and introduces an overhead field - add an optional
typevalue which defaults tohost= bad because overhead field - create an entire new
inlinePluginsfields = bad because of a lot of duplicated code or merging is required - add an explicit check in the first case that errs if both values are set = bad because a bit ugly
@mloiseleur WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @jnoordsij — agreed. My goal here was to avoid any breaking changes. Since localPlugin only landed in v37.0.0, it may not be widely used yet, but changing its shape now would still be a breaking change. One alternative would be keeping the current key path and introduce the “more organized” structure alongside it, then deprecate the old one gradually. Not sure how this kind of situation is handled for this helm chart.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cilasbeltrame What kind of "more organized" structure you have in mind ?
On helm chart, it's quite rare to maintain both solution and deprecate.
Usually, we:
- Advertise the breaking changes: major version + upgrade instructions
- Add a check in
requirements.yamlthat will fail gracefully in case user did not notice the change despite 1.
See here for an example PR of breaking change about plugins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking to implement what @jnoordsij said:
- having a
typePluginwith the options of:inline,hostand possibly alocalPath, cause having hostPath is not that well recommended, especially for security reasons and it requires additional work to pull the plugins from a repo or blob storage for example and save it into k8s nodes filesystem.localPathcould be another future feature using Blob storage with PV/PVC, taking advantage of safer solutions such as s3-csi-driver or FUSE CSI driver for Google. - keep backwards compatibility with both and alert it to be depricated soon
- next major version, we remove the backward compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds perfectly fine to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jnoordsij could you pls check the commit b33f4f5
where I have enabled backwards compatibility with previous localPlugin declaration + added two new options: inlinePlugin (config map based) and localPathPlugin where we can mount plugins using pod mountpoints, it allows us to extend storage support such as PVC, CSI drivers (s3-csi-driver, FUSE), and other volume types.
…ame:cilasbeltrame/traefik-helm-chart into feat/support-cm-local-plugins
|
@jnoordsij Anything left on your side ? |
darkweaver87
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks, it's a great addition. Just see my comment please :-)
d794c78 to
b33f4f5
Compare
|
@mloiseleur , @jnoordsij, to not make @cilasbeltrame loose his time reverting and implementing again, we might agree on specifications on that topic. On my side, I thought about a different approach in order to keep the code clean and consistent. configMap: experimental:
localPlugins:
inline-demo:
moduleName: github.com/traefik/inlinedemo
mountPath: /plugins-local/src/github.com/traefik/inlinedemo
from:
configMap:
name: mysrc
namespace: defaulthostPath: experimental:
localPlugins:
inline-demo:
moduleName: github.com/traefik/inlinedemo
mountPath: /plugins-local/src/github.com/traefik/inlinedemo
from:
hostPath: /plugins-local/src/github.com/traefik/barFrom here, user will have to:
I also don't think we necessarily need backward compatibility and we can break here. People we need to update their values while updating to new major release. We can just raise an error if they are using the old config. WDTY ? |
|
@darkweaver87 I agree with you that we can introduce breaking change, if needed. One of the need/motivation outlined by @cilasbeltrame in the head of this PR is:
So I'm not sure that this proposal with On my side, I'm ok with your previous proposal. @jnoordsij Any comment ? |
|
I think both proposals @darkweaver87 are perfectly fine; you could extend the second even with allowing In terms of preference, I still like the idea of the explicit For comparison: in the second proposal, if one would add both As for whether or not allowing source code: as @darkweaver87 correctly pointed out, this can be achieved in a similar manner with the All in all: I do agree implementing just the suggestion from #1492 (comment) should be preferred the way to go here. |
|
@cilasbeltrame,thanks for your patience :-) |
No problem - the PR grew a bit more than I expected! |
jnoordsij
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks great and almost done. Having the deprecation notice is really nice! Just a few last technical changes to tackle now.
Apart from that, we may want to update the values to ensure the schema properly reflects the new structure, but given there's no schema defined yet anyways, I'm also fine with doing that in a follow-up to no longer delay merging this.
traefik-crds/README.md
Outdated
| @@ -0,0 +1,39 @@ | |||
| # traefik-crds | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not exactly sure what happened here, but this README (and also the one in the traefik chart directory) are autogenerated and thus should not be committed; definitely not as part of this PR.
@mloiseleur should we gitignore these maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jnoordsij this README should be removed from the PR.
There is only one README.md at the root of this project and it's not auto-generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that makes sense 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just figured out these are actually the output from make docs, however outputted as README.md rather than VALUES.md.
@cilasbeltrame can you remove this file (and the one in traefik chart) from this PR, then run make docs and ensure it updates VALUES.md rather than adding readmes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jnoordsij That's correct, let me review this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jnoordsij just did it, I've removed the files. When running make docs it didn't update the files values.md, my gues is because I haven't changed the existent schema:
experimental.localPlugins | object | {}
It should be good. Pls confirm.
cilasbeltrame in traefik-helm-chart on feat/support-cm-local-plugins [⇣$✘?]
❯ make docs
docker run --rm -v "/Users/cilasbeltrame/Documents/projects/opensource/traefik-helm-chart:/helm-docs" jnorwood/helm-docs:v1.14.2 -o VALUES.md
time="2025-11-06T09:41:54Z" level=info msg="Found Chart directories [traefik, traefik-crds]"
time="2025-11-06T09:41:54Z" level=info msg="Generating README Documentation for chart traefik-crds"
time="2025-11-06T09:41:54Z" level=info msg="Generating README Documentation for chart traefik"
…ame:cilasbeltrame/traefik-helm-chart into feat/support-cm-local-plugins
Co-authored-by: Michel Loiseleur <[email protected]>
Co-authored-by: Michel Loiseleur <[email protected]>
Co-authored-by: Michel Loiseleur <[email protected]>
Co-authored-by: Michel Loiseleur <[email protected]>
…ame:cilasbeltrame/traefik-helm-chart into feat/support-cm-local-plugins
darkweaver87
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it with the following values:
experimental:
localPlugins:
demo-inline:
moduleName: github.com/traefik/plugindemo
mountPath: /plugins-local/src/github.com/traefik/plugindemo
type: inlinePlugin
source:
go.mod: |
module github.com/traefik/plugindemo
go 1.19
.traefik.yml: |
displayName: Demo Plugin
type: middleware
iconPath: .assets/icon.png
import: github.com/traefik/plugindemo
summary: '[Demo] Add Request Header'
testData:
Headers:
X-Demo: test
X-URL: '{{URL}}'
demo.go: |
// Package plugindemo a demo plugin.
package plugindemo
import (
"bytes"
"context"
"fmt"
"net/http"
"text/template"
)
// Config the plugin configuration.
type Config struct {
Headers map[string]string `json:"headers,omitempty"`
}
// CreateConfig creates the default plugin configuration.
func CreateConfig() *Config {
return &Config{
Headers: make(map[string]string),
}
}
// Demo a Demo plugin.
type Demo struct {
next http.Handler
headers map[string]string
name string
template *template.Template
}
// New created a new Demo plugin.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
if len(config.Headers) == 0 {
return nil, fmt.Errorf("headers cannot be empty")
}
return &Demo{
headers: config.Headers,
next: next,
name: name,
template: template.New("demo").Delims("[[", "]]"),
}, nil
}
func (a *Demo) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
for key, value := range a.headers {
tmpl, err := a.template.Parse(value)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
writer := &bytes.Buffer{}
err = tmpl.Execute(writer, req)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
req.Header.Set(key, writer.String())
}
a.next.ServeHTTP(rw, req)
}
# Test the warning; BTW traefik should ignore the error with abortOnPluginFailure but that's not the case
#demo-legacy-hostpath:
# moduleName: github.com/traefik/plugindemolegacyhostpath
# mountPath: /plugins-local/src/github.com/traefik/plugindemolegacyhostpath
# hostPath: /tmp/plugin-source # This will trigger deprecation warning; just to test, path doesn't exist
demo-localpath:
moduleName: github.com/traefik/plugindemolocalpath
mountPath: /plugins-local/src/github.com/traefik/plugindemolocalpath
type: localPath
volumeName: plugindemolocalpath
extraObjects:
- apiVersion: v1
kind: ConfigMap
metadata:
name: "plugindemo-localpath"
data:
go.mod: |
module github.com/traefik/plugindemolocalpath
go 1.19
.traefik.yml: |
displayName: Demo Plugin
type: middleware
iconPath: .assets/icon.png
import: github.com/traefik/plugindemolocalpath
summary: '[Demo] Add Request Header'
testData:
Headers:
X-Demo: test
X-URL: '{{"{{"}}URL{{"}}"}}'
demo.go: |
// Package plugindemolocalpath a demo plugin.
package plugindemolocalpath
import (
"bytes"
"context"
"fmt"
"net/http"
"text/template"
)
// Config the plugin configuration.
type Config struct {
Headers map[string]string `json:"headers,omitempty"`
}
// CreateConfig creates the default plugin configuration.
func CreateConfig() *Config {
return &Config{
Headers: make(map[string]string),
}
}
// Demo a Demo plugin.
type Demo struct {
next http.Handler
headers map[string]string
name string
template *template.Template
}
// New created a new Demo plugin.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
if len(config.Headers) == 0 {
return nil, fmt.Errorf("headers cannot be empty")
}
return &Demo{
headers: config.Headers,
next: next,
name: name,
template: template.New("demo").Delims("[[", "]]"),
}, nil
}
func (a *Demo) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
for key, value := range a.headers {
tmpl, err := a.template.Parse(value)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
writer := &bytes.Buffer{}
err = tmpl.Execute(writer, req)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
req.Header.Set(key, writer.String())
}
a.next.ServeHTTP(rw, req)
}
- apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami:latest
ports:
- containerPort: 80
- apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- port: 80
targetPort: 80
selector:
app: whoami
- apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: demo-inline
spec:
plugin:
demo-inline:
headers:
X-Demo: "demo"
- apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: demo-localpath
spec:
plugin:
demo-localpath:
headers:
X-Demo: "demo-localpath"
- apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: whoami-test
spec:
entryPoints:
- web
routes:
- match: Host(`whoami.localhost`) || PathPrefix(`/whoami`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: demo-inline
- match: Host(`whoami.localhost`) || PathPrefix(`/whoami2`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: demo-localpath
ingressRoute:
dashboard:
enabled: true
logs:
general:
level: INFO
access:
enabled: true
deployment:
additionalVolumes:
- name: plugindemolocalpath
configMap:
name: plugindemo-localpath
It misses a control on mountPath which should be required and produces:
$ helm upgrade --install traefik ./traefik -f test-pr1492-values.yaml -n traefik --create-namespace
Error: UPGRADE FAILED: cannot patch "traefik" with kind Deployment: Deployment.apps "traefik" is invalid: spec.template.spec.containers[0].volumeMounts[3].mountPath: Required value
otherwize LGTM
traefik-crds/README.md
Outdated
| @@ -0,0 +1,39 @@ | |||
| # traefik-crds | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jnoordsij this README should be removed from the PR.
There is only one README.md at the root of this project and it's not auto-generated.
| {{- end -}} | ||
|
|
||
| {{- define "traefik.localPluginCmName" -}} | ||
| {{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }} | |
| {{ include "traefik.fullname" .context }}-local-plugin-{{ .pluginName | replace "." "-" }} |
| {{- $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 }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {{- $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 }} | |
| {{- $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 }} |
| {{- 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 }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {{- 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 }} | |
| {{- 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 }} |
| {{/* 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 -}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {{/* 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 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 -}} |
| {{- 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 }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| {{- 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 }} | |
| {{- 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: {{ $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 }} |
@darkweaver87 I think you are having old ref on your local, using your values file I'm able to test it sucessfully from my end. Pls review and confirm. |
What does this PR do?
This PR adds ConfigMap support for local plugins in the
experimental.localPluginsconfiguration, providing an alternative to the existinghostPathapproach. The enhancement allows users to embed plugin source code directly in their Helm values, which the chart automatically converts into Kubernetes ConfigMaps and mounts into Traefik containers.Key Changes:
values.yamlwith automatic ConfigMap generation from inline plugin content (up to 1MiB - etcd limit)Example configuration:
Motivation
The current hostPath approach for local plugins has several limitations in containerized environments:
Problems with hostPath:
More
make testand all the tests passed