From c815a732ef0451df64eb9ef49deb364063bca592 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Mon, 3 Dec 2018 11:32:05 +0100 Subject: [PATCH] Migrate rest provider --- cmd/configuration.go | 2 +- config/static/static_config.go | 2 +- integration/fixtures/rest/simple.toml | 12 ++++ integration/integration_test.go | 1 + integration/resources/compose/rest.yml | 4 ++ integration/rest_test.go | 73 ++++++++++++++++++++++ provider/aggregator/aggregator.go | 4 ++ provider/rest/rest.go | 67 ++++++++++++++++++++ server/router/route_appender_aggregator.go | 4 +- server/router/router_test.go | 15 +++++ server/service/service.go | 5 +- 11 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 integration/fixtures/rest/simple.toml create mode 100644 integration/resources/compose/rest.yml create mode 100644 integration/rest_test.go create mode 100644 provider/rest/rest.go diff --git a/cmd/configuration.go b/cmd/configuration.go index 1cf25aa5e..23b9137ec 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -19,10 +19,10 @@ import ( "github.com/containous/traefik/old/provider/marathon" "github.com/containous/traefik/old/provider/mesos" "github.com/containous/traefik/old/provider/rancher" - "github.com/containous/traefik/old/provider/rest" "github.com/containous/traefik/old/provider/zk" "github.com/containous/traefik/ping" "github.com/containous/traefik/provider/file" + "github.com/containous/traefik/provider/rest" "github.com/containous/traefik/tracing/datadog" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" diff --git a/config/static/static_config.go b/config/static/static_config.go index ee8f1a420..8c4370c54 100644 --- a/config/static/static_config.go +++ b/config/static/static_config.go @@ -20,11 +20,11 @@ import ( "github.com/containous/traefik/old/provider/marathon" "github.com/containous/traefik/old/provider/mesos" "github.com/containous/traefik/old/provider/rancher" - "github.com/containous/traefik/old/provider/rest" "github.com/containous/traefik/old/provider/zk" "github.com/containous/traefik/ping" acmeprovider "github.com/containous/traefik/provider/acme" "github.com/containous/traefik/provider/file" + "github.com/containous/traefik/provider/rest" "github.com/containous/traefik/tls" "github.com/containous/traefik/tracing/datadog" "github.com/containous/traefik/tracing/jaeger" diff --git a/integration/fixtures/rest/simple.toml b/integration/fixtures/rest/simple.toml new file mode 100644 index 000000000..fef571d46 --- /dev/null +++ b/integration/fixtures/rest/simple.toml @@ -0,0 +1,12 @@ + +[entryPoints] + [entryPoints.http] + address = ":8000" + +[api] + +[log] +logLevel = "DEBUG" + +[providers] + [providers.rest] diff --git a/integration/integration_test.go b/integration/integration_test.go index e6f7aee2f..1b2a04b17 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -60,6 +60,7 @@ func init() { check.Suite(&AcmeSuite{}) check.Suite(&ErrorPagesSuite{}) check.Suite(&FileSuite{}) + check.Suite(&RestSuite{}) check.Suite(&GRPCSuite{}) check.Suite(&HealthCheckSuite{}) check.Suite(&HTTPSSuite{}) diff --git a/integration/resources/compose/rest.yml b/integration/resources/compose/rest.yml new file mode 100644 index 000000000..fba4da55d --- /dev/null +++ b/integration/resources/compose/rest.yml @@ -0,0 +1,4 @@ +whoami1: + image: containous/whoami + ports: + - "8881:80" diff --git a/integration/rest_test.go b/integration/rest_test.go new file mode 100644 index 000000000..0169b0868 --- /dev/null +++ b/integration/rest_test.go @@ -0,0 +1,73 @@ +package integration + +import ( + "bytes" + "encoding/json" + "net/http" + "time" + + "github.com/containous/traefik/config" + "github.com/containous/traefik/integration/try" + "github.com/go-check/check" + checker "github.com/vdemeester/shakers" +) + +type RestSuite struct{ BaseSuite } + +func (s *RestSuite) SetUpSuite(c *check.C) { + s.createComposeProject(c, "rest") + + s.composeProject.Start(c) +} + +func (s *RestSuite) TestSimpleConfiguration(c *check.C) { + cmd, display := s.traefikCmd(withConfigFile("fixtures/rest/simple.toml")) + + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // Expected a 404 as we did not configure anything. + err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + config := config.Configuration{ + Routers: map[string]*config.Router{ + "router1": { + EntryPoints: []string{"http"}, + Middlewares: []string{}, + Service: "service1", + Rule: "PathPrefix:/", + }, + }, + Services: map[string]*config.Service{ + "service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80", + Weight: 1, + }, + }, + }, + }, + }, + } + + json, err := json.Marshal(config) + c.Assert(err, checker.IsNil) + + request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(json)) + c.Assert(err, checker.IsNil) + + response, err := http.DefaultClient.Do(request) + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + + err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix:/")) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) +} diff --git a/provider/aggregator/aggregator.go b/provider/aggregator/aggregator.go index fa0933e0a..4e4a9eaa5 100644 --- a/provider/aggregator/aggregator.go +++ b/provider/aggregator/aggregator.go @@ -23,6 +23,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator { p.quietAddProvider(conf.File) } + if conf.Rest != nil { + p.quietAddProvider(conf.Rest) + } + return p } diff --git a/provider/rest/rest.go b/provider/rest/rest.go new file mode 100644 index 000000000..aa245ac3e --- /dev/null +++ b/provider/rest/rest.go @@ -0,0 +1,67 @@ +package rest + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/containous/mux" + "github.com/containous/traefik/config" + "github.com/containous/traefik/log" + "github.com/containous/traefik/provider" + "github.com/containous/traefik/safe" + "github.com/unrolled/render" +) + +var _ provider.Provider = (*Provider)(nil) + +// Provider is a provider.Provider implementation that provides a Rest API. +type Provider struct { + configurationChan chan<- config.Message + EntryPoint string `description:"EntryPoint" export:"true"` +} + +var templatesRenderer = render.New(render.Options{Directory: "nowhere"}) + +// Init the provider. +func (p *Provider) Init() error { + return nil +} + +// 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 + } + + configuration := new(config.Configuration) + body, _ := ioutil.ReadAll(request.Body) + err := json.Unmarshal(body, configuration) + if err == nil { + p.configurationChan <- config.Message{ProviderName: "rest", Configuration: configuration} + err := templatesRenderer.JSON(response, http.StatusOK, configuration) + if err != nil { + log.WithoutContext().Error(err) + } + } else { + log.WithoutContext().Errorf("Error parsing configuration %+v", err) + http.Error(response, fmt.Sprintf("%+v", err), http.StatusBadRequest) + } + }) +} + +// Provide allows the provider to provide configurations to traefik +// using the given configuration channel. +func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.Pool) error { + p.configurationChan = configurationChan + return nil +} diff --git a/server/router/route_appender_aggregator.go b/server/router/route_appender_aggregator.go index d9600be22..0e8652969 100644 --- a/server/router/route_appender_aggregator.go +++ b/server/router/route_appender_aggregator.go @@ -24,7 +24,9 @@ func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder, aggregator := &RouteAppenderAggregator{} - // FIXME add REST + if conf.Providers != nil && conf.Providers.Rest != nil { + aggregator.AddAppender(conf.Providers.Rest) + } if conf.API != nil && conf.API.EntryPoint == entryPointName { chain, err := chainBuilder.BuildChain(ctx, conf.API.Middlewares) diff --git a/server/router/router_test.go b/server/router/router_test.go index b3fba1505..b133faacf 100644 --- a/server/router/router_test.go +++ b/server/router/router_test.go @@ -59,6 +59,21 @@ func TestRouterManager_Get(t *testing.T) { entryPoints: []string{"web"}, expected: ExpectedResult{StatusCode: http.StatusOK}, }, + { + desc: "no load balancer", + routersConfig: map[string]*config.Router{ + "foo": { + EntryPoints: []string{"web"}, + Service: "foo-service", + Rule: "Host:foo.bar", + }, + }, + serviceConfig: map[string]*config.Service{ + "foo-service": {}, + }, + entryPoints: []string{"web"}, + expected: ExpectedResult{StatusCode: http.StatusNotFound}, + }, { desc: "no middleware, default entry point", routersConfig: map[string]*config.Router{ diff --git a/server/service/service.go b/server/service/service.go index c053a7152..cfbb45c8d 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -61,7 +61,10 @@ func (m *Manager) Build(rootCtx context.Context, serviceName string, responseMod // TODO refactor ? if conf, ok := m.configs[serviceName]; ok { // FIXME Should handle multiple service types - return m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier) + if conf.LoadBalancer != nil { + return m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier) + } + return nil, fmt.Errorf("the service %q doesn't have any load balancer", serviceName) } return nil, fmt.Errorf("the service %q does not exits", serviceName) }