1
0
Fork 0

Add internal provider

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-11-14 16:40:05 +01:00 committed by Traefiker Bot
parent 2ee2e29262
commit 424e2a9439
71 changed files with 2523 additions and 1469 deletions

View file

@ -35,8 +35,11 @@ func (c *challengeHTTP) Timeout() (timeout, interval time.Duration) {
return 60 * time.Second, 5 * time.Second
}
// Append adds routes on internal router
func (p *Provider) Append(router *mux.Router) {
// CreateHandler creates a HTTP handler to expose the token for the HTTP challenge.
func (p *Provider) CreateHandler(notFoundHandler http.Handler) http.Handler {
router := mux.NewRouter().SkipClean(true)
router.NotFoundHandler = notFoundHandler
router.Methods(http.MethodGet).
Path(http01.ChallengePath("{token}")).
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -64,6 +67,8 @@ func (p *Provider) Append(router *mux.Router) {
}
rw.WriteHeader(http.StatusNotFound)
}))
return router
}
func getTokenValue(ctx context.Context, token, domain string, store ChallengeStore) []byte {

View file

@ -3,7 +3,6 @@ package rest
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/containous/traefik/v2/pkg/config/dynamic"
@ -23,8 +22,7 @@ type Provider struct {
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
}
func (p *Provider) SetDefaults() {}
var templatesRenderer = render.New(render.Options{Directory: "nowhere"})
@ -33,40 +31,32 @@ func (p *Provider) Init() error {
return nil
}
// Handler creates an http.Handler for the Rest API
func (p *Provider) Handler() http.Handler {
// CreateRouter creates a router for the Rest API
func (p *Provider) CreateRouter() *mux.Router {
router := mux.NewRouter()
p.Append(router)
router.Methods(http.MethodPut).Path("/api/providers/{provider}").Handler(p)
return router
}
// Append add rest provider routes on a router.
func (p *Provider) Append(systemRouter *mux.Router) {
systemRouter.
Methods(http.MethodPut).
Path("/api/providers/{provider}").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
if vars["provider"] != "rest" {
response.WriteHeader(http.StatusBadRequest)
fmt.Fprint(response, "Only 'rest' provider can be updated through the REST API")
return
}
func (p *Provider) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
if vars["provider"] != "rest" {
http.Error(rw, "Only 'rest' provider can be updated through the REST API", http.StatusBadRequest)
return
}
configuration := new(dynamic.Configuration)
body, _ := ioutil.ReadAll(request.Body)
configuration := new(dynamic.Configuration)
if err := json.Unmarshal(body, configuration); err != nil {
log.WithoutContext().Errorf("Error parsing configuration %+v", err)
http.Error(response, fmt.Sprintf("%+v", err), http.StatusBadRequest)
return
}
if err := json.NewDecoder(req.Body).Decode(configuration); err != nil {
log.WithoutContext().Errorf("Error parsing configuration %+v", err)
http.Error(rw, fmt.Sprintf("%+v", err), http.StatusBadRequest)
return
}
p.configurationChan <- dynamic.Message{ProviderName: "rest", Configuration: configuration}
if err := templatesRenderer.JSON(response, http.StatusOK, configuration); err != nil {
log.WithoutContext().Error(err)
}
})
p.configurationChan <- dynamic.Message{ProviderName: "rest", Configuration: configuration}
if err := templatesRenderer.JSON(rw, http.StatusOK, configuration); err != nil {
log.WithoutContext().Error(err)
}
}
// Provide allows the provider to provide configurations to traefik

View file

@ -0,0 +1,49 @@
{
"http": {
"routers": {
"api": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806
},
"dashboard": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805
}
},
"middlewares": {
"dashboard_redirect": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
}
},
"dashboard_stripprefix": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
}
}
},
"services": {
"api": {},
"dashboard": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"api": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806
}
},
"services": {
"api": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,10 @@
{
"http": {
"services": {
"api": {},
"dashboard": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"api": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,76 @@
{
"http": {
"routers": {
"api": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806
},
"dashboard": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805
},
"ping": {
"entryPoints": [
"test"
],
"service": "ping@internal",
"rule": "PathPrefix(`/ping`)",
"priority": 9223372036854775807
},
"prometheus": {
"entryPoints": [
"test"
],
"service": "prometheus@internal",
"rule": "PathPrefix(`/metrics`)",
"priority": 9223372036854775807
},
"rest": {
"entryPoints": [
"traefik"
],
"service": "rest@internal",
"rule": "PathPrefix(`/api/providers`)",
"priority": 9223372036854775807
}
},
"middlewares": {
"dashboard_redirect": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
}
},
"dashboard_stripprefix": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
}
}
},
"services": {
"api": {},
"dashboard": {},
"ping": {},
"prometheus": {},
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,13 @@
{
"http": {
"services": {
"api": {},
"dashboard": {},
"ping": {},
"prometheus": {},
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"ping": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"ping": {
"entryPoints": [
"test"
],
"service": "ping@internal",
"rule": "PathPrefix(`/ping`)",
"priority": 9223372036854775807
}
},
"services": {
"ping": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"prometheus": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"prometheus": {
"entryPoints": [
"test"
],
"service": "prometheus@internal",
"rule": "PathPrefix(`/metrics`)",
"priority": 9223372036854775807
}
},
"services": {
"prometheus": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"rest": {
"entryPoints": [
"traefik"
],
"service": "rest@internal",
"rule": "PathPrefix(`/api/providers`)",
"priority": 9223372036854775807
}
},
"services": {
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,156 @@
package traefik
import (
"math"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/tls"
)
var _ provider.Provider = (*Provider)(nil)
// Provider is a provider.Provider implementation that provides the internal routers.
type Provider struct {
staticCfg static.Configuration
}
// New creates a new instance of the internal provider.
func New(staticCfg static.Configuration) *Provider {
return &Provider{staticCfg: staticCfg}
}
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
func (i *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
configurationChan <- dynamic.Message{
ProviderName: "internal",
Configuration: i.createConfiguration(),
}
return nil
}
// Init the provider.
func (i *Provider) Init() error {
return nil
}
func (i *Provider) createConfiguration() *dynamic.Configuration {
cfg := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
},
TCP: &dynamic.TCPConfiguration{
Routers: make(map[string]*dynamic.TCPRouter),
Services: make(map[string]*dynamic.TCPService),
},
TLS: &dynamic.TLSConfiguration{
Stores: make(map[string]tls.Store),
Options: make(map[string]tls.Options),
},
}
i.apiConfiguration(cfg)
i.pingConfiguration(cfg)
i.restConfiguration(cfg)
i.prometheusConfiguration(cfg)
return cfg
}
func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.API == nil {
return
}
if i.staticCfg.API.Insecure {
cfg.HTTP.Routers["api"] = &dynamic.Router{
EntryPoints: []string{"traefik"},
Service: "api@internal",
Priority: math.MaxInt64 - 1,
Rule: "PathPrefix(`/api`)",
}
if i.staticCfg.API.Dashboard {
cfg.HTTP.Routers["dashboard"] = &dynamic.Router{
EntryPoints: []string{"traefik"},
Service: "dashboard@internal",
Priority: math.MaxInt64 - 2,
Rule: "PathPrefix(`/`)",
Middlewares: []string{"dashboard_redirect@internal", "dashboard_stripprefix@internal"},
}
cfg.HTTP.Middlewares["dashboard_redirect"] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `^(http:\/\/[^:]+(:\d+)?)/$`,
Replacement: "${1}/dashboard/",
Permanent: true,
},
}
cfg.HTTP.Middlewares["dashboard_stripprefix"] = &dynamic.Middleware{
StripPrefix: &dynamic.StripPrefix{Prefixes: []string{"/dashboard/", "/dashboard"}},
}
}
}
cfg.HTTP.Services["api"] = &dynamic.Service{}
if i.staticCfg.API.Dashboard {
cfg.HTTP.Services["dashboard"] = &dynamic.Service{}
}
}
func (i *Provider) pingConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Ping == nil {
return
}
if !i.staticCfg.Ping.ManualRouting {
cfg.HTTP.Routers["ping"] = &dynamic.Router{
EntryPoints: []string{i.staticCfg.Ping.EntryPoint},
Service: "ping@internal",
Priority: math.MaxInt64,
Rule: "PathPrefix(`/ping`)",
}
}
cfg.HTTP.Services["ping"] = &dynamic.Service{}
}
func (i *Provider) restConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Providers == nil || i.staticCfg.Providers.Rest == nil {
return
}
if i.staticCfg.Providers.Rest.Insecure {
cfg.HTTP.Routers["rest"] = &dynamic.Router{
EntryPoints: []string{"traefik"},
Service: "rest@internal",
Priority: math.MaxInt64,
Rule: "PathPrefix(`/api/providers`)",
}
}
cfg.HTTP.Services["rest"] = &dynamic.Service{}
}
func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Metrics == nil || i.staticCfg.Metrics.Prometheus == nil {
return
}
if !i.staticCfg.Metrics.Prometheus.ManualRouting {
cfg.HTTP.Routers["prometheus"] = &dynamic.Router{
EntryPoints: []string{i.staticCfg.Metrics.Prometheus.EntryPoint},
Service: "prometheus@internal",
Priority: math.MaxInt64,
Rule: "PathPrefix(`/metrics`)",
}
}
cfg.HTTP.Services["prometheus"] = &dynamic.Service{}
}

View file

@ -0,0 +1,199 @@
package traefik
import (
"encoding/json"
"flag"
"io/ioutil"
"path/filepath"
"testing"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/ping"
"github.com/containous/traefik/v2/pkg/provider/rest"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures")
func Test_createConfiguration(t *testing.T) {
testCases := []struct {
desc string
staticCfg static.Configuration
}{
{
desc: "full_configuration.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: true,
Dashboard: true,
},
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: false,
},
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: true,
},
},
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: false,
},
},
},
},
{
desc: "full_configuration_secure.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: false,
Dashboard: true,
},
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: true,
},
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: false,
},
},
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: true,
},
},
},
},
{
desc: "api_insecure_with_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: true,
Dashboard: true,
},
},
},
{
desc: "api_insecure_without_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: true,
Dashboard: false,
},
},
},
{
desc: "api_secure_with_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: false,
Dashboard: true,
},
},
},
{
desc: "api_secure_without_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: false,
Dashboard: false,
},
},
},
{
desc: "ping_simple.json",
staticCfg: static.Configuration{
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: false,
},
},
},
{
desc: "ping_custom.json",
staticCfg: static.Configuration{
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: true,
},
},
},
{
desc: "rest_insecure.json",
staticCfg: static.Configuration{
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: true,
},
},
},
},
{
desc: "rest_secure.json",
staticCfg: static.Configuration{
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: false,
},
},
},
},
{
desc: "prometheus_simple.json",
staticCfg: static.Configuration{
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: false,
},
},
},
},
{
desc: "prometheus_custom.json",
staticCfg: static.Configuration{
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: true,
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := Provider{staticCfg: test.staticCfg}
cfg := provider.createConfiguration()
filename := filepath.Join("fixtures", test.desc)
if *updateExpected {
newJSON, err := json.MarshalIndent(cfg, "", " ")
require.NoError(t, err)
err = ioutil.WriteFile(filename, newJSON, 0644)
require.NoError(t, err)
}
expectedJSON, err := ioutil.ReadFile(filename)
require.NoError(t, err)
actualJSON, err := json.MarshalIndent(cfg, "", " ")
require.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(actualJSON))
})
}
}