2020-07-15 14:56:03 +00:00
package http
import (
"context"
2024-02-07 16:14:07 +00:00
"errors"
2020-07-15 14:56:03 +00:00
"fmt"
"hash/fnv"
2021-03-04 19:08:03 +00:00
"io"
2020-07-15 14:56:03 +00:00
"net/http"
2024-10-29 14:30:38 +00:00
"strings"
2020-07-15 14:56:03 +00:00
"time"
"github.com/cenkalti/backoff/v4"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2020-08-17 16:04:03 +00:00
"github.com/traefik/paerser/file"
ptypes "github.com/traefik/paerser/types"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/job"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
2020-07-15 14:56:03 +00:00
)
var _ provider . Provider = ( * Provider ) ( nil )
// Provider is a provider.Provider implementation that queries an HTTP(s) endpoint for a configuration.
type Provider struct {
2022-10-14 13:10:10 +00:00
Endpoint string ` description:"Load configuration from this endpoint." json:"endpoint" toml:"endpoint" yaml:"endpoint" `
PollInterval ptypes . Duration ` description:"Polling interval for endpoint." json:"pollInterval,omitempty" toml:"pollInterval,omitempty" yaml:"pollInterval,omitempty" export:"true" `
PollTimeout ptypes . Duration ` description:"Polling timeout for endpoint." json:"pollTimeout,omitempty" toml:"pollTimeout,omitempty" yaml:"pollTimeout,omitempty" export:"true" `
Headers map [ string ] string ` description:"Define custom headers to be sent to the endpoint." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true" `
TLS * types . ClientTLS ` description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" `
2020-07-15 14:56:03 +00:00
httpClient * http . Client
lastConfigurationHash uint64
}
// SetDefaults sets the default values.
func ( p * Provider ) SetDefaults ( ) {
2020-08-17 16:04:03 +00:00
p . PollInterval = ptypes . Duration ( 5 * time . Second )
p . PollTimeout = ptypes . Duration ( 5 * time . Second )
2020-07-15 14:56:03 +00:00
}
// Init the provider.
func ( p * Provider ) Init ( ) error {
if p . Endpoint == "" {
2024-02-07 16:14:07 +00:00
return errors . New ( "non-empty endpoint is required" )
2020-07-15 14:56:03 +00:00
}
if p . PollInterval <= 0 {
2024-02-07 16:14:07 +00:00
return errors . New ( "poll interval must be greater than 0" )
2020-07-15 14:56:03 +00:00
}
p . httpClient = & http . Client {
Timeout : time . Duration ( p . PollTimeout ) ,
}
if p . TLS != nil {
tlsConfig , err := p . TLS . CreateTLSConfig ( context . Background ( ) )
if err != nil {
2021-10-26 08:54:11 +00:00
return fmt . Errorf ( "unable to create client TLS configuration: %w" , err )
2020-07-15 14:56:03 +00:00
}
p . httpClient . Transport = & http . Transport {
TLSClientConfig : tlsConfig ,
}
}
return nil
}
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
pool . GoCtx ( func ( routineCtx context . Context ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( routineCtx ) . With ( ) . Str ( logs . ProviderName , "http" ) . Logger ( )
ctxLog := logger . WithContext ( routineCtx )
2020-07-15 14:56:03 +00:00
operation := func ( ) error {
2022-06-24 10:34:08 +00:00
if err := p . updateConfiguration ( configurationChan ) ; err != nil {
return err
}
2020-07-15 14:56:03 +00:00
ticker := time . NewTicker ( time . Duration ( p . PollInterval ) )
defer ticker . Stop ( )
for {
select {
case <- ticker . C :
2022-06-24 10:34:08 +00:00
if err := p . updateConfiguration ( configurationChan ) ; err != nil {
return err
2020-07-15 14:56:03 +00:00
}
case <- routineCtx . Done ( ) :
return nil
}
}
}
notify := func ( err error , time time . Duration ) {
2022-11-30 08:50:05 +00:00
logger . Error ( ) . Err ( err ) . Msgf ( "Provider error, retrying in %s" , time )
2020-07-15 14:56:03 +00:00
}
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , backoff . WithContext ( job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , ctxLog ) , notify )
if err != nil {
2022-11-30 08:50:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Cannot retrieve data" )
2020-07-15 14:56:03 +00:00
}
} )
return nil
}
2022-06-24 10:34:08 +00:00
func ( p * Provider ) updateConfiguration ( configurationChan chan <- dynamic . Message ) error {
configData , err := p . fetchConfigurationData ( )
if err != nil {
return fmt . Errorf ( "cannot fetch configuration data: %w" , err )
}
fnvHasher := fnv . New64 ( )
if _ , err = fnvHasher . Write ( configData ) ; err != nil {
return fmt . Errorf ( "cannot hash configuration data: %w" , err )
}
hash := fnvHasher . Sum64 ( )
if hash == p . lastConfigurationHash {
return nil
}
p . lastConfigurationHash = hash
configuration , err := decodeConfiguration ( configData )
if err != nil {
return fmt . Errorf ( "cannot decode configuration data: %w" , err )
}
configurationChan <- dynamic . Message {
ProviderName : "http" ,
Configuration : configuration ,
}
return nil
}
2020-07-15 14:56:03 +00:00
// fetchConfigurationData fetches the configuration data from the configured endpoint.
func ( p * Provider ) fetchConfigurationData ( ) ( [ ] byte , error ) {
2022-10-14 13:10:10 +00:00
req , err := http . NewRequest ( http . MethodGet , p . Endpoint , http . NoBody )
if err != nil {
return nil , fmt . Errorf ( "create fetch request: %w" , err )
}
for k , v := range p . Headers {
2024-10-29 14:30:38 +00:00
if strings . EqualFold ( k , "Host" ) {
req . Host = v
} else {
req . Header . Set ( k , v )
}
2022-10-14 13:10:10 +00:00
}
res , err := p . httpClient . Do ( req )
2020-07-15 14:56:03 +00:00
if err != nil {
2022-10-14 13:10:10 +00:00
return nil , fmt . Errorf ( "do fetch request: %w" , err )
2020-07-15 14:56:03 +00:00
}
defer res . Body . Close ( )
if res . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "received non-ok response code: %d" , res . StatusCode )
}
2021-03-04 19:08:03 +00:00
return io . ReadAll ( res . Body )
2020-07-15 14:56:03 +00:00
}
// decodeConfiguration decodes and returns the dynamic configuration from the given data.
func decodeConfiguration ( data [ ] byte ) ( * dynamic . Configuration , error ) {
configuration := & dynamic . Configuration {
HTTP : & dynamic . HTTPConfiguration {
2020-09-11 13:40:03 +00:00
Routers : make ( map [ string ] * dynamic . Router ) ,
Middlewares : make ( map [ string ] * dynamic . Middleware ) ,
Services : make ( map [ string ] * dynamic . Service ) ,
ServersTransports : make ( map [ string ] * dynamic . ServersTransport ) ,
2020-07-15 14:56:03 +00:00
} ,
TCP : & dynamic . TCPConfiguration {
2022-12-09 08:58:05 +00:00
Routers : make ( map [ string ] * dynamic . TCPRouter ) ,
Services : make ( map [ string ] * dynamic . TCPService ) ,
ServersTransports : make ( map [ string ] * dynamic . TCPServersTransport ) ,
2020-07-15 14:56:03 +00:00
} ,
TLS : & dynamic . TLSConfiguration {
Stores : make ( map [ string ] tls . Store ) ,
Options : make ( map [ string ] tls . Options ) ,
} ,
UDP : & dynamic . UDPConfiguration {
Routers : make ( map [ string ] * dynamic . UDPRouter ) ,
Services : make ( map [ string ] * dynamic . UDPService ) ,
} ,
}
err := file . DecodeContent ( string ( data ) , ".yaml" , configuration )
if err != nil {
return nil , err
}
return configuration , nil
}