feat: plugins integration.
This commit is contained in:
parent
58bf1a2ca5
commit
0186c31d59
26 changed files with 1025 additions and 51 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,3 +16,4 @@
|
||||||
*.exe
|
*.exe
|
||||||
cover.out
|
cover.out
|
||||||
vendor/
|
vendor/
|
||||||
|
plugins-storage/
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
text = "string `traefik` has (\\d) occurrences, make it a constant"
|
text = "string `traefik` has (\\d) occurrences, make it a constant"
|
||||||
[[issues.exclude-rules]]
|
[[issues.exclude-rules]]
|
||||||
path = "pkg/server/middleware/middlewares.go"
|
path = "pkg/server/middleware/middlewares.go"
|
||||||
text = "Function 'buildConstructor' is too long \\(\\d+ > 230\\)"
|
text = "Function 'buildConstructor' has too many statements"
|
||||||
[[issues.exclude-rules]] # FIXME must be fixed
|
[[issues.exclude-rules]] # FIXME must be fixed
|
||||||
path = "cmd/context.go"
|
path = "cmd/context.go"
|
||||||
text = "S1000: should use a simple channel send/receive instead of `select` with a single case"
|
text = "S1000: should use a simple channel send/receive instead of `select` with a single case"
|
||||||
|
|
42
cmd/traefik/plugins.go
Normal file
42
cmd/traefik/plugins.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containous/traefik/v2/pkg/config/static"
|
||||||
|
"github.com/containous/traefik/v2/pkg/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
const outputDir = "./plugins-storage/"
|
||||||
|
|
||||||
|
func initPlugins(staticCfg *static.Configuration) (*plugins.Client, map[string]plugins.Descriptor, *plugins.DevPlugin, error) {
|
||||||
|
if !isPilotEnabled(staticCfg) || !hasPlugins(staticCfg) {
|
||||||
|
return nil, map[string]plugins.Descriptor{}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := plugins.ClientOptions{
|
||||||
|
Output: outputDir,
|
||||||
|
Token: staticCfg.Experimental.Pilot.Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := plugins.NewClient(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = plugins.Setup(client, staticCfg.Experimental.Plugins, staticCfg.Experimental.DevPlugin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, staticCfg.Experimental.Plugins, staticCfg.Experimental.DevPlugin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPilotEnabled(staticCfg *static.Configuration) bool {
|
||||||
|
return staticCfg.Experimental != nil &&
|
||||||
|
staticCfg.Experimental.Pilot != nil &&
|
||||||
|
staticCfg.Experimental.Pilot.Token != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPlugins(staticCfg *static.Configuration) bool {
|
||||||
|
return staticCfg.Experimental != nil &&
|
||||||
|
len(staticCfg.Experimental.Plugins) > 0 || staticCfg.Experimental.DevPlugin != nil
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/containous/traefik/v2/pkg/metrics"
|
"github.com/containous/traefik/v2/pkg/metrics"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
|
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
|
||||||
"github.com/containous/traefik/v2/pkg/pilot"
|
"github.com/containous/traefik/v2/pkg/pilot"
|
||||||
|
"github.com/containous/traefik/v2/pkg/plugins"
|
||||||
"github.com/containous/traefik/v2/pkg/provider/acme"
|
"github.com/containous/traefik/v2/pkg/provider/acme"
|
||||||
"github.com/containous/traefik/v2/pkg/provider/aggregator"
|
"github.com/containous/traefik/v2/pkg/provider/aggregator"
|
||||||
"github.com/containous/traefik/v2/pkg/provider/traefik"
|
"github.com/containous/traefik/v2/pkg/provider/traefik"
|
||||||
|
@ -119,6 +120,12 @@ func runCmd(staticConfiguration *static.Configuration) error {
|
||||||
|
|
||||||
ctx := cmd.ContextWithSignal(context.Background())
|
ctx := cmd.ContextWithSignal(context.Background())
|
||||||
|
|
||||||
|
if staticConfiguration.Experimental != nil && staticConfiguration.Experimental.DevPlugin != nil {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, 30*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
if staticConfiguration.Ping != nil {
|
if staticConfiguration.Ping != nil {
|
||||||
staticConfiguration.Ping.WithContext(ctx)
|
staticConfiguration.Ping.WithContext(ctx)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +199,18 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
accessLog := setupAccessLog(staticConfiguration.AccessLog)
|
accessLog := setupAccessLog(staticConfiguration.AccessLog)
|
||||||
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
|
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
|
||||||
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry)
|
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry)
|
||||||
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder)
|
|
||||||
|
client, plgs, devPlugin, err := initPlugins(staticConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginBuilder, err := plugins.NewBuilder(client, plgs, devPlugin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder)
|
||||||
|
|
||||||
var defaultEntryPoints []string
|
var defaultEntryPoints []string
|
||||||
for name, cfg := range staticConfiguration.EntryPoints {
|
for name, cfg := range staticConfiguration.EntryPoints {
|
||||||
|
|
|
@ -159,6 +159,24 @@ ReadTimeout is the maximum duration for reading the entire request, including th
|
||||||
`--entrypoints.<name>.transport.respondingtimeouts.writetimeout`:
|
`--entrypoints.<name>.transport.respondingtimeouts.writetimeout`:
|
||||||
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
|
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
|
||||||
|
|
||||||
|
`--experimental.devplugin.gopath`:
|
||||||
|
plugin's GOPATH.
|
||||||
|
|
||||||
|
`--experimental.devplugin.modulename`:
|
||||||
|
plugin's module name.
|
||||||
|
|
||||||
|
`--experimental.pilot.token`:
|
||||||
|
Traefik Pilot token.
|
||||||
|
|
||||||
|
`--experimental.plugins.<name>`:
|
||||||
|
Plugins configuration. (Default: ```false```)
|
||||||
|
|
||||||
|
`--experimental.plugins.<name>.modulename`:
|
||||||
|
plugin's module name.
|
||||||
|
|
||||||
|
`--experimental.plugins.<name>.version`:
|
||||||
|
plugin's version.
|
||||||
|
|
||||||
`--global.checknewversion`:
|
`--global.checknewversion`:
|
||||||
Periodically check if a new version has been released. (Default: ```false```)
|
Periodically check if a new version has been released. (Default: ```false```)
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,24 @@ ReadTimeout is the maximum duration for reading the entire request, including th
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`:
|
||||||
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
|
WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_DEVPLUGIN_GOPATH`:
|
||||||
|
plugin's GOPATH.
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_DEVPLUGIN_MODULENAME`:
|
||||||
|
plugin's module name.
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_PILOT_TOKEN`:
|
||||||
|
Traefik Pilot token.
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_PLUGINS_<NAME>`:
|
||||||
|
Plugins configuration. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_PLUGINS_<NAME>_MODULENAME`:
|
||||||
|
plugin's module name.
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_PLUGINS_<NAME>_VERSION`:
|
||||||
|
plugin's version.
|
||||||
|
|
||||||
`TRAEFIK_GLOBAL_CHECKNEWVERSION`:
|
`TRAEFIK_GLOBAL_CHECKNEWVERSION`:
|
||||||
Periodically check if a new version has been released. (Default: ```false```)
|
Periodically check if a new version has been released. (Default: ```false```)
|
||||||
|
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -20,6 +20,7 @@ require (
|
||||||
github.com/cenkalti/backoff/v4 v4.0.0
|
github.com/cenkalti/backoff/v4 v4.0.0
|
||||||
github.com/containerd/containerd v1.3.2 // indirect
|
github.com/containerd/containerd v1.3.2 // indirect
|
||||||
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
|
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
|
||||||
|
github.com/containous/yaegi v0.8.13
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0
|
github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0
|
||||||
|
@ -57,6 +58,7 @@ require (
|
||||||
github.com/miekg/dns v1.1.27
|
github.com/miekg/dns v1.1.27
|
||||||
github.com/mitchellh/copystructure v1.0.0
|
github.com/mitchellh/copystructure v1.0.0
|
||||||
github.com/mitchellh/hashstructure v1.0.0
|
github.com/mitchellh/hashstructure v1.0.0
|
||||||
|
github.com/mitchellh/mapstructure v1.3.2
|
||||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
|
@ -83,6 +85,7 @@ require (
|
||||||
github.com/vulcand/predicate v1.1.0
|
github.com/vulcand/predicate v1.1.0
|
||||||
go.elastic.co/apm v1.7.0
|
go.elastic.co/apm v1.7.0
|
||||||
go.elastic.co/apm/module/apmot v1.7.0
|
go.elastic.co/apm/module/apmot v1.7.0
|
||||||
|
golang.org/x/mod v0.2.0
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||||
google.golang.org/grpc v1.27.1
|
google.golang.org/grpc v1.27.1
|
||||||
|
@ -90,6 +93,7 @@ require (
|
||||||
gopkg.in/fsnotify.v1 v1.4.7
|
gopkg.in/fsnotify.v1 v1.4.7
|
||||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||||
k8s.io/api v0.18.2
|
k8s.io/api v0.18.2
|
||||||
k8s.io/apimachinery v0.18.2
|
k8s.io/apimachinery v0.18.2
|
||||||
k8s.io/client-go v0.18.2
|
k8s.io/client-go v0.18.2
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -160,6 +160,8 @@ github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba h1:PhR03pep+5e
|
||||||
github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba/go.mod h1:zkWcASFUJEst6QwCrxLdkuw1gvaKqmflEipm+iecV5M=
|
github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba/go.mod h1:zkWcASFUJEst6QwCrxLdkuw1gvaKqmflEipm+iecV5M=
|
||||||
github.com/containous/mux v0.0.0-20181024131434-c33f32e26898 h1:1srn9voikJGofblBhWy3WuZWqo14Ou7NaswNG/I2yWc=
|
github.com/containous/mux v0.0.0-20181024131434-c33f32e26898 h1:1srn9voikJGofblBhWy3WuZWqo14Ou7NaswNG/I2yWc=
|
||||||
github.com/containous/mux v0.0.0-20181024131434-c33f32e26898/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
|
github.com/containous/mux v0.0.0-20181024131434-c33f32e26898/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
|
||||||
|
github.com/containous/yaegi v0.8.13 h1:ADhAZok9av2wxge4/LxNqZHG/+rwHZcwpSLKmy9cz48=
|
||||||
|
github.com/containous/yaegi v0.8.13/go.mod h1:Yj82MHpXQ9/h3ukzc2numJQ/Wr4+M3C9YLMzNjFtd3o=
|
||||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
|
github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
|
||||||
|
@ -520,6 +522,8 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
||||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
|
||||||
|
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
@ -1019,6 +1023,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -38,6 +38,8 @@ type Middleware struct {
|
||||||
PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"`
|
PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"`
|
||||||
Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"`
|
Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"`
|
||||||
ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty"`
|
ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty"`
|
||||||
|
|
||||||
|
Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
27
pkg/config/dynamic/plugins.go
Normal file
27
pkg/config/dynamic/plugins.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package dynamic
|
||||||
|
|
||||||
|
import "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=false
|
||||||
|
|
||||||
|
// PluginConf holds the plugin configuration.
|
||||||
|
type PluginConf map[string]interface{}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PluginConf) DeepCopyInto(out *PluginConf) {
|
||||||
|
if in == nil {
|
||||||
|
*out = nil
|
||||||
|
} else {
|
||||||
|
*out = runtime.DeepCopyJSON(*in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginConf.
|
||||||
|
func (in *PluginConf) DeepCopy() *PluginConf {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PluginConf)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
|
@ -730,6 +730,13 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
||||||
*out = new(ContentType)
|
*out = new(ContentType)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.Plugin != nil {
|
||||||
|
in, out := &in.Plugin, &out.Plugin
|
||||||
|
*out = make(map[string]PluginConf, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = *val.DeepCopy()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
pkg/config/static/experimental.go
Normal file
16
pkg/config/static/experimental.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package static
|
||||||
|
|
||||||
|
import "github.com/containous/traefik/v2/pkg/plugins"
|
||||||
|
|
||||||
|
// Experimental experimental Traefik features.
|
||||||
|
type Experimental struct {
|
||||||
|
Pilot *Pilot `description:"Traefik Pilot configuration." json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty"`
|
||||||
|
|
||||||
|
Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty"`
|
||||||
|
DevPlugin *plugins.DevPlugin `description:"Dev plugin configuration." json:"devPlugin,omitempty" toml:"devPlugin,omitempty" yaml:"devPlugin,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pilot Configuration related to Traefik Pilot.
|
||||||
|
type Pilot struct {
|
||||||
|
Token string `description:"Traefik Pilot token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||||
|
}
|
|
@ -74,16 +74,6 @@ type Configuration struct {
|
||||||
Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty"`
|
Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Experimental the experimental feature configuration.
|
|
||||||
type Experimental struct {
|
|
||||||
Pilot *PilotConfiguration `description:"Pilot configuration." json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty" export:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PilotConfiguration holds pilot configuration.
|
|
||||||
type PilotConfiguration struct {
|
|
||||||
Token string `description:"Pilot token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" export:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificateResolver contains the configuration for the different types of certificates resolver.
|
// CertificateResolver contains the configuration for the different types of certificates resolver.
|
||||||
type CertificateResolver struct {
|
type CertificateResolver struct {
|
||||||
ACME *acmeprovider.Configuration `description:"Enable ACME (Let's Encrypt): automatic SSL." json:"acme,omitempty" toml:"acme,omitempty" yaml:"acme,omitempty" export:"true"`
|
ACME *acmeprovider.Configuration `description:"Enable ACME (Let's Encrypt): automatic SSL." json:"acme,omitempty" toml:"acme,omitempty" yaml:"acme,omitempty" export:"true"`
|
||||||
|
|
166
pkg/plugins/builder.go
Normal file
166
pkg/plugins/builder.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containous/yaegi/interp"
|
||||||
|
"github.com/containous/yaegi/stdlib"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
const devPluginName = "dev"
|
||||||
|
|
||||||
|
// pluginContext The static part of a plugin configuration.
|
||||||
|
type pluginContext struct {
|
||||||
|
// GoPath plugin's GOPATH
|
||||||
|
GoPath string `json:"goPath,omitempty" toml:"goPath,omitempty" yaml:"goPath,omitempty"`
|
||||||
|
|
||||||
|
// Import plugin's import/package
|
||||||
|
Import string `json:"import,omitempty" toml:"import,omitempty" yaml:"import,omitempty"`
|
||||||
|
|
||||||
|
// BasePkg plugin's base package name (optional)
|
||||||
|
BasePkg string `json:"basePkg,omitempty" toml:"basePkg,omitempty" yaml:"basePkg,omitempty"`
|
||||||
|
|
||||||
|
interpreter *interp.Interpreter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder is a plugin builder.
|
||||||
|
type Builder struct {
|
||||||
|
descriptors map[string]pluginContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new Builder.
|
||||||
|
func NewBuilder(client *Client, plugins map[string]Descriptor, devPlugin *DevPlugin) (*Builder, error) {
|
||||||
|
pb := &Builder{
|
||||||
|
descriptors: map[string]pluginContext{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for pName, desc := range plugins {
|
||||||
|
manifest, err := client.ReadManifest(desc.ModuleName)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.ResetAll()
|
||||||
|
return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := interp.New(interp.Options{GoPath: client.GoPath()})
|
||||||
|
i.Use(stdlib.Symbols)
|
||||||
|
|
||||||
|
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.descriptors[pName] = pluginContext{
|
||||||
|
interpreter: i,
|
||||||
|
GoPath: client.GoPath(),
|
||||||
|
Import: manifest.Import,
|
||||||
|
BasePkg: manifest.BasePkg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if devPlugin != nil {
|
||||||
|
manifest, err := ReadManifest(devPlugin.GoPath, devPlugin.ModuleName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to read manifest: %w", devPlugin.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := interp.New(interp.Options{GoPath: devPlugin.GoPath})
|
||||||
|
i.Use(stdlib.Symbols)
|
||||||
|
|
||||||
|
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", devPlugin.ModuleName, manifest.Import, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.descriptors[devPluginName] = pluginContext{
|
||||||
|
interpreter: i,
|
||||||
|
GoPath: devPlugin.GoPath,
|
||||||
|
Import: manifest.Import,
|
||||||
|
BasePkg: manifest.BasePkg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build builds a plugin.
|
||||||
|
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (*Middleware, error) {
|
||||||
|
if b.descriptors == nil {
|
||||||
|
return nil, fmt.Errorf("plugin: no plugin definition in the static configuration: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor, ok := b.descriptors[pName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("plugin: unknown plugin type: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMiddleware(descriptor, config, middlewareName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware is a HTTP handler plugin wrapper.
|
||||||
|
type Middleware struct {
|
||||||
|
middlewareName string
|
||||||
|
fnNew reflect.Value
|
||||||
|
config reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMiddleware(descriptor pluginContext, config map[string]interface{}, middlewareName string) (*Middleware, error) {
|
||||||
|
basePkg := descriptor.BasePkg
|
||||||
|
if basePkg == "" {
|
||||||
|
basePkg = strings.ReplaceAll(path.Base(descriptor.Import), "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
vConfig, err := descriptor.interpreter.Eval(basePkg + `.CreateConfig()`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("plugin: failed to eval CreateConfig: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &mapstructure.DecoderConfig{
|
||||||
|
DecodeHook: mapstructure.StringToSliceHookFunc(","),
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: vConfig.Interface(),
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := mapstructure.NewDecoder(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("plugin: failed to create configuration decoder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decoder.Decode(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("plugin: failed to decode configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnNew, err := descriptor.interpreter.Eval(basePkg + `.New`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("plugin: failed to eval New: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Middleware{
|
||||||
|
middlewareName: middlewareName,
|
||||||
|
fnNew: fnNew,
|
||||||
|
config: vConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new HTTP handler.
|
||||||
|
func (m *Middleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
||||||
|
args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(next), m.config, reflect.ValueOf(m.middlewareName)}
|
||||||
|
results := m.fnNew.Call(args)
|
||||||
|
|
||||||
|
if len(results) > 1 && results[1].Interface() != nil {
|
||||||
|
return nil, results[1].Interface().(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, ok := results[0].Interface().(http.Handler)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("plugin: invalid handler type: %T", results[0].Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler, nil
|
||||||
|
}
|
415
pkg/plugins/client.go
Normal file
415
pkg/plugins/client.go
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
zipa "archive/zip"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/mod/module"
|
||||||
|
"golang.org/x/mod/zip"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sourcesFolder = "sources"
|
||||||
|
archivesFolder = "archives"
|
||||||
|
stateFilename = "state.json"
|
||||||
|
goPathSrc = "src"
|
||||||
|
pluginManifest = ".traefik.yml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const pilotURL = "https://plugin.pilot.traefik.io/public/"
|
||||||
|
|
||||||
|
const (
|
||||||
|
hashHeader = "X-Plugin-Hash"
|
||||||
|
tokenHeader = "X-Token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientOptions the options of a Traefik Pilot client.
|
||||||
|
type ClientOptions struct {
|
||||||
|
Output string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client a Traefik Pilot client.
|
||||||
|
type Client struct {
|
||||||
|
HTTPClient *http.Client
|
||||||
|
baseURL *url.URL
|
||||||
|
|
||||||
|
token string
|
||||||
|
archives string
|
||||||
|
stateFile string
|
||||||
|
goPath string
|
||||||
|
sources string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new Traefik Pilot client.
|
||||||
|
func NewClient(opts ClientOptions) (*Client, error) {
|
||||||
|
baseURL, err := url.Parse(pilotURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourcesRootPath := filepath.Join(filepath.FromSlash(opts.Output), sourcesFolder)
|
||||||
|
err = resetDirectory(sourcesRootPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
goPath, err := ioutil.TempDir(sourcesRootPath, "gop-*")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create GoPath: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
archivesPath := filepath.Join(filepath.FromSlash(opts.Output), archivesFolder)
|
||||||
|
err = os.MkdirAll(archivesPath, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create archives directory %s: %w", archivesPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
||||||
|
baseURL: baseURL,
|
||||||
|
|
||||||
|
archives: archivesPath,
|
||||||
|
stateFile: filepath.Join(archivesPath, stateFilename),
|
||||||
|
|
||||||
|
goPath: goPath,
|
||||||
|
sources: filepath.Join(goPath, goPathSrc),
|
||||||
|
|
||||||
|
token: opts.Token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoPath gets the plugins GoPath.
|
||||||
|
func (c *Client) GoPath() string {
|
||||||
|
return c.goPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadManifest reads a plugin manifest.
|
||||||
|
func (c *Client) ReadManifest(moduleName string) (*Manifest, error) {
|
||||||
|
return ReadManifest(c.goPath, moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadManifest reads a plugin manifest.
|
||||||
|
func ReadManifest(goPath, moduleName string) (*Manifest, error) {
|
||||||
|
p := filepath.Join(goPath, goPathSrc, filepath.FromSlash(moduleName), pluginManifest)
|
||||||
|
|
||||||
|
file, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open the plugin manifest %s: %w", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
m := &Manifest{}
|
||||||
|
err = yaml.NewDecoder(file).Decode(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode the plugin manifest %s: %w", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download downloads a plugin archive.
|
||||||
|
func (c *Client) Download(ctx context.Context, pName, pVersion string) (string, error) {
|
||||||
|
filename := c.buildArchivePath(pName, pVersion)
|
||||||
|
|
||||||
|
var hash string
|
||||||
|
_, err := os.Stat(filename)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return "", fmt.Errorf("failed to read archive %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
hash, err = computeHash(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to compute hash: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "download", pName, pVersion))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse endpoint URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hash != "" {
|
||||||
|
req.Header.Set(hashHeader, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.token != "" {
|
||||||
|
req.Header.Set(tokenHeader, c.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to call service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
err = os.MkdirAll(filepath.Dir(filename), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var file *os.File
|
||||||
|
file, err = os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create file %q: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to write response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err = computeHash(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to compute hash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusNotModified {
|
||||||
|
// noop
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
return "", fmt.Errorf("error: %d: %s", resp.StatusCode, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check checks the plugin archive integrity.
|
||||||
|
func (c *Client) Check(ctx context.Context, pName, pVersion, hash string) error {
|
||||||
|
endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "validate", pName, pVersion))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse endpoint URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hash != "" {
|
||||||
|
req.Header.Set(hashHeader, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.token != "" {
|
||||||
|
req.Header.Set(tokenHeader, c.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to call service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("plugin integrity check failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unzip unzip a plugin archive.
|
||||||
|
func (c *Client) Unzip(pName, pVersion string) error {
|
||||||
|
err := c.unzipModule(pName, pVersion)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.unzipArchive(pName, pVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) unzipModule(pName, pVersion string) error {
|
||||||
|
src := c.buildArchivePath(pName, pVersion)
|
||||||
|
dest := filepath.Join(c.sources, filepath.FromSlash(pName))
|
||||||
|
|
||||||
|
return zip.Unzip(dest, module.Version{Path: pName, Version: pVersion}, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) unzipArchive(pName, pVersion string) error {
|
||||||
|
zipPath := c.buildArchivePath(pName, pVersion)
|
||||||
|
|
||||||
|
archive, err := zipa.OpenReader(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = archive.Close() }()
|
||||||
|
|
||||||
|
dest := filepath.Join(c.sources, filepath.FromSlash(pName))
|
||||||
|
|
||||||
|
for _, f := range archive.File {
|
||||||
|
err = unzipFile(f, dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unzipFile(f *zipa.File, dest string) error {
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = rc.Close() }()
|
||||||
|
|
||||||
|
pathParts := strings.SplitN(f.Name, string(os.PathSeparator), 2)
|
||||||
|
p := filepath.Join(dest, pathParts[1])
|
||||||
|
|
||||||
|
if f.FileInfo().IsDir() {
|
||||||
|
return os.MkdirAll(p, f.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Dir(p), 0o750)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
elt, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = elt.Close() }()
|
||||||
|
|
||||||
|
_, err = io.Copy(elt, rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanArchives cleans plugins archives.
|
||||||
|
func (c *Client) CleanArchives(plugins map[string]Descriptor) error {
|
||||||
|
if _, err := os.Stat(c.stateFile); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stateFile, err := os.Open(c.stateFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open state file %s: %w", c.stateFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
previous := make(map[string]string)
|
||||||
|
err = json.NewDecoder(stateFile).Decode(&previous)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode state file %s: %w", c.stateFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for pName, pVersion := range previous {
|
||||||
|
for _, desc := range plugins {
|
||||||
|
if desc.ModuleName == pName && desc.Version != pVersion {
|
||||||
|
archivePath := c.buildArchivePath(pName, pVersion)
|
||||||
|
if err = os.RemoveAll(archivePath); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove archive %s: %w", archivePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteState writes the plugins state files.
|
||||||
|
func (c *Client) WriteState(plugins map[string]Descriptor) error {
|
||||||
|
m := make(map[string]string)
|
||||||
|
|
||||||
|
for _, descriptor := range plugins {
|
||||||
|
m[descriptor.ModuleName] = descriptor.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
mp, err := json.MarshalIndent(m, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(c.stateFile, mp, 0o600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAll resets all plugins related directories.
|
||||||
|
func (c *Client) ResetAll() error {
|
||||||
|
if c.goPath == "" {
|
||||||
|
return errors.New("goPath is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := resetDirectory(filepath.Join(c.goPath, ".."))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resetDirectory(c.archives)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) buildArchivePath(pName, pVersion string) string {
|
||||||
|
return filepath.Join(c.archives, filepath.FromSlash(pName), pVersion+".zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetDirectory(dir string) error {
|
||||||
|
dirPath, err := filepath.Abs(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(currentPath, dirPath) {
|
||||||
|
return fmt.Errorf("cannot be deleted: the directory path %s is the parent of the current path %s", dirPath, currentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.MkdirAll(dir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeHash(filepath string) (string, error) {
|
||||||
|
file, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
|
||||||
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := hash.Sum(nil)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", sum), nil
|
||||||
|
}
|
146
pkg/plugins/plugins.go
Normal file
146
pkg/plugins/plugins.go
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup setup plugins environment.
|
||||||
|
func Setup(client *Client, plugins map[string]Descriptor, devPlugin *DevPlugin) error {
|
||||||
|
err := checkPluginsConfiguration(plugins)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.CleanArchives(plugins)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clean archives: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for pAlias, desc := range plugins {
|
||||||
|
log.FromContext(ctx).Debugf("loading of plugin: %s: %s@%s", pAlias, desc.ModuleName, desc.Version)
|
||||||
|
|
||||||
|
hash, err := client.Download(ctx, desc.ModuleName, desc.Version)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.ResetAll()
|
||||||
|
return fmt.Errorf("failed to download plugin %s: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Check(ctx, desc.ModuleName, desc.Version, hash)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.ResetAll()
|
||||||
|
return fmt.Errorf("failed to check archive integrity of the plugin %s: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.WriteState(plugins)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.ResetAll()
|
||||||
|
return fmt.Errorf("failed to write plugins state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, desc := range plugins {
|
||||||
|
err = client.Unzip(desc.ModuleName, desc.Version)
|
||||||
|
if err != nil {
|
||||||
|
_ = client.ResetAll()
|
||||||
|
return fmt.Errorf("failed to unzip archive: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if devPlugin != nil {
|
||||||
|
err := checkDevPluginConfiguration(devPlugin)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid configuration: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDevPluginConfiguration(plugin *DevPlugin) error {
|
||||||
|
if plugin == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if plugin.GoPath == "" {
|
||||||
|
return errors.New("missing Go Path (prefer a dedicated Go Path)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if plugin.ModuleName == "" {
|
||||||
|
return errors.New("missing module name")
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := ReadManifest(plugin.GoPath, plugin.ModuleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Type != "middleware" {
|
||||||
|
return errors.New("unsupported type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Import == "" {
|
||||||
|
return errors.New("missing import")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(m.Import, plugin.ModuleName) {
|
||||||
|
return fmt.Errorf("the import %q must be related to the module name %q", m.Import, plugin.ModuleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.DisplayName == "" {
|
||||||
|
return errors.New("missing DisplayName")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Summary == "" {
|
||||||
|
return errors.New("missing Summary")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.TestData == nil {
|
||||||
|
return errors.New("missing TestData")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPluginsConfiguration(plugins map[string]Descriptor) error {
|
||||||
|
if plugins == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq := make(map[string]struct{})
|
||||||
|
|
||||||
|
var errs []string
|
||||||
|
for pAlias, descriptor := range plugins {
|
||||||
|
if descriptor.ModuleName == "" {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: plugin name is missing", pAlias))
|
||||||
|
}
|
||||||
|
|
||||||
|
if descriptor.Version == "" {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: plugin version is missing", pAlias))
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(descriptor.ModuleName, "/") || strings.HasSuffix(descriptor.ModuleName, "/") {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: plugin name should not start or end with a /", pAlias))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := uniq[descriptor.ModuleName]; ok {
|
||||||
|
errs = append(errs, fmt.Sprintf("only one version of a plugin is allowed, there is a duplicate of %s", descriptor.ModuleName))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq[descriptor.ModuleName] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New(strings.Join(errs, ": "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
30
pkg/plugins/types.go
Normal file
30
pkg/plugins/types.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
// Descriptor The static part of a plugin configuration (prod).
|
||||||
|
type Descriptor struct {
|
||||||
|
// ModuleName (required)
|
||||||
|
ModuleName string `description:"plugin's module name." json:"moduleName,omitempty" toml:"moduleName,omitempty" yaml:"moduleName,omitempty"`
|
||||||
|
|
||||||
|
// Version (required)
|
||||||
|
Version string `description:"plugin's version." json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DevPlugin The static part of a plugin configuration (only for dev).
|
||||||
|
type DevPlugin struct {
|
||||||
|
// GoPath plugin's GOPATH. (required)
|
||||||
|
GoPath string `description:"plugin's GOPATH." json:"goPath,omitempty" toml:"goPath,omitempty" yaml:"goPath,omitempty"`
|
||||||
|
|
||||||
|
// ModuleName (required)
|
||||||
|
ModuleName string `description:"plugin's module name." json:"moduleName,omitempty" toml:"moduleName,omitempty" yaml:"moduleName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manifest The plugin manifest.
|
||||||
|
type Manifest struct {
|
||||||
|
DisplayName string `yaml:"displayName"`
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Import string `yaml:"import"`
|
||||||
|
BasePkg string `yaml:"basePkg"`
|
||||||
|
Compatibility string `yaml:"compatibility"`
|
||||||
|
Summary string `yaml:"summary"`
|
||||||
|
TestData map[string]interface{} `yaml:"testData"`
|
||||||
|
}
|
|
@ -232,6 +232,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||||
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
|
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
|
||||||
Retry: middleware.Spec.Retry,
|
Retry: middleware.Spec.Retry,
|
||||||
ContentType: middleware.Spec.ContentType,
|
ContentType: middleware.Spec.ContentType,
|
||||||
|
Plugin: middleware.Spec.Plugin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ type MiddlewareSpec struct {
|
||||||
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
|
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
|
||||||
Retry *dynamic.Retry `json:"retry,omitempty"`
|
Retry *dynamic.Retry `json:"retry,omitempty"`
|
||||||
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
|
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
|
||||||
|
Plugin map[string]dynamic.PluginConf `json:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
|
@ -687,6 +687,13 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
|
||||||
*out = new(dynamic.ContentType)
|
*out = new(dynamic.ContentType)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.Plugin != nil {
|
||||||
|
in, out := &in.Plugin, &out.Plugin
|
||||||
|
*out = make(map[string]dynamic.PluginConf, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = *val.DeepCopy()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/stripprefix"
|
"github.com/containous/traefik/v2/pkg/middlewares/stripprefix"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/stripprefixregex"
|
"github.com/containous/traefik/v2/pkg/middlewares/stripprefixregex"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/tracing"
|
"github.com/containous/traefik/v2/pkg/middlewares/tracing"
|
||||||
|
"github.com/containous/traefik/v2/pkg/plugins"
|
||||||
"github.com/containous/traefik/v2/pkg/server/provider"
|
"github.com/containous/traefik/v2/pkg/server/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ const (
|
||||||
// Builder the middleware builder.
|
// Builder the middleware builder.
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
configs map[string]*runtime.MiddlewareInfo
|
configs map[string]*runtime.MiddlewareInfo
|
||||||
|
pluginBuilder *plugins.Builder
|
||||||
serviceBuilder serviceBuilder
|
serviceBuilder serviceBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,8 +50,8 @@ type serviceBuilder interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuilder creates a new Builder.
|
// NewBuilder creates a new Builder.
|
||||||
func NewBuilder(configs map[string]*runtime.MiddlewareInfo, serviceBuilder serviceBuilder) *Builder {
|
func NewBuilder(configs map[string]*runtime.MiddlewareInfo, serviceBuilder serviceBuilder, pluginBuilder *plugins.Builder) *Builder {
|
||||||
return &Builder{configs: configs, serviceBuilder: serviceBuilder}
|
return &Builder{configs: configs, serviceBuilder: serviceBuilder, pluginBuilder: pluginBuilder}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildChain creates a middleware chain.
|
// BuildChain creates a middleware chain.
|
||||||
|
@ -338,6 +340,27 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plugin
|
||||||
|
if config.Plugin != nil {
|
||||||
|
if middleware != nil {
|
||||||
|
return nil, badConf
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginType, rawPluginConfig, err := findPluginConfig(config.Plugin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := b.pluginBuilder.Build(pluginType, rawPluginConfig, middlewareName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
|
return m.NewHandler(ctx, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if middleware == nil {
|
if middleware == nil {
|
||||||
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) {
|
||||||
testConfig := map[string]*runtime.MiddlewareInfo{
|
testConfig := map[string]*runtime.MiddlewareInfo{
|
||||||
"empty": {},
|
"empty": {},
|
||||||
}
|
}
|
||||||
middlewaresBuilder := NewBuilder(testConfig, nil)
|
middlewaresBuilder := NewBuilder(testConfig, nil, nil)
|
||||||
|
|
||||||
chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"})
|
chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"})
|
||||||
_, err := chain.Then(nil)
|
_, err := chain.Then(nil)
|
||||||
|
@ -29,7 +29,7 @@ func TestBuilder_BuildChainNonExistentChain(t *testing.T) {
|
||||||
testConfig := map[string]*runtime.MiddlewareInfo{
|
testConfig := map[string]*runtime.MiddlewareInfo{
|
||||||
"foobar": {},
|
"foobar": {},
|
||||||
}
|
}
|
||||||
middlewaresBuilder := NewBuilder(testConfig, nil)
|
middlewaresBuilder := NewBuilder(testConfig, nil, nil)
|
||||||
|
|
||||||
chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"})
|
chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"})
|
||||||
_, err := chain.Then(nil)
|
_, err := chain.Then(nil)
|
||||||
|
@ -270,7 +270,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
|
||||||
Middlewares: test.configuration,
|
Middlewares: test.configuration,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
builder := NewBuilder(rtConf.Middlewares, nil)
|
builder := NewBuilder(rtConf.Middlewares, nil, nil)
|
||||||
|
|
||||||
result := builder.BuildChain(ctx, test.buildChain)
|
result := builder.BuildChain(ctx, test.buildChain)
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ func TestBuilder_buildConstructor(t *testing.T) {
|
||||||
Middlewares: testConfig,
|
Middlewares: testConfig,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
middlewaresBuilder := NewBuilder(rtConf.Middlewares, nil)
|
middlewaresBuilder := NewBuilder(rtConf.Middlewares, nil, nil)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
32
pkg/server/middleware/plugins.go
Normal file
32
pkg/server/middleware/plugins.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[string]interface{}, error) {
|
||||||
|
if len(rawConfig) != 1 {
|
||||||
|
return "", nil, errors.New("plugin: invalid configuration: no configuration or too many plugin definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginType string
|
||||||
|
var rawPluginConfig map[string]interface{}
|
||||||
|
|
||||||
|
for pType, pConfig := range rawConfig {
|
||||||
|
pluginType = pType
|
||||||
|
rawPluginConfig = pConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if pluginType == "" {
|
||||||
|
return "", nil, errors.New("plugin: missing plugin type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rawPluginConfig) == 0 {
|
||||||
|
return "", nil, fmt.Errorf("plugin: missing plugin configuration: %s", pluginType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginType, rawPluginConfig, nil
|
||||||
|
}
|
|
@ -289,7 +289,7 @@ func TestRouterManager_Get(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||||
|
|
||||||
|
@ -394,7 +394,7 @@ func TestAccessLog(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||||
|
|
||||||
|
@ -682,7 +682,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
|
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
|
||||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||||
|
|
||||||
|
@ -764,7 +764,7 @@ func TestProviderOnMiddlewares(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
|
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
|
||||||
chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil)
|
||||||
|
|
||||||
|
@ -825,7 +825,7 @@ func BenchmarkRouterServe(b *testing.B) {
|
||||||
})
|
})
|
||||||
|
|
||||||
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
|
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||||
"github.com/containous/traefik/v2/pkg/config/static"
|
"github.com/containous/traefik/v2/pkg/config/static"
|
||||||
"github.com/containous/traefik/v2/pkg/log"
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
|
"github.com/containous/traefik/v2/pkg/plugins"
|
||||||
"github.com/containous/traefik/v2/pkg/responsemodifiers"
|
"github.com/containous/traefik/v2/pkg/responsemodifiers"
|
||||||
"github.com/containous/traefik/v2/pkg/server/middleware"
|
"github.com/containous/traefik/v2/pkg/server/middleware"
|
||||||
"github.com/containous/traefik/v2/pkg/server/router"
|
"github.com/containous/traefik/v2/pkg/server/router"
|
||||||
|
@ -26,12 +27,14 @@ type RouterFactory struct {
|
||||||
|
|
||||||
managerFactory *service.ManagerFactory
|
managerFactory *service.ManagerFactory
|
||||||
|
|
||||||
|
pluginBuilder *plugins.Builder
|
||||||
|
|
||||||
chainBuilder *middleware.ChainBuilder
|
chainBuilder *middleware.ChainBuilder
|
||||||
tlsManager *tls.Manager
|
tlsManager *tls.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouterFactory creates a new RouterFactory.
|
// NewRouterFactory creates a new RouterFactory.
|
||||||
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder) *RouterFactory {
|
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder, pluginBuilder *plugins.Builder) *RouterFactory {
|
||||||
var entryPointsTCP, entryPointsUDP []string
|
var entryPointsTCP, entryPointsUDP []string
|
||||||
for name, cfg := range staticConfiguration.EntryPoints {
|
for name, cfg := range staticConfiguration.EntryPoints {
|
||||||
protocol, err := cfg.GetProtocol()
|
protocol, err := cfg.GetProtocol()
|
||||||
|
@ -53,6 +56,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
||||||
managerFactory: managerFactory,
|
managerFactory: managerFactory,
|
||||||
tlsManager: tlsManager,
|
tlsManager: tlsManager,
|
||||||
chainBuilder: chainBuilder,
|
chainBuilder: chainBuilder,
|
||||||
|
pluginBuilder: pluginBuilder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +67,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
||||||
// HTTP
|
// HTTP
|
||||||
serviceManager := f.managerFactory.Build(rtConf)
|
serviceManager := f.managerFactory.Build(rtConf)
|
||||||
|
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
||||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||||
|
|
||||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
|
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
|
||||||
|
|
|
@ -51,7 +51,7 @@ func TestReuseService(t *testing.T) {
|
||||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
|
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
|
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
||||||
|
|
||||||
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
|
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
||||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
|
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
|
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
||||||
|
|
||||||
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)}))
|
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)}))
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ func TestInternalServices(t *testing.T) {
|
||||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
|
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
|
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
||||||
|
|
||||||
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
|
entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue