2019-11-14 16:40:05 +01:00
package server
import (
2020-02-03 17:56:04 +01:00
"context"
2019-11-14 16:40:05 +01:00
"encoding/json"
"reflect"
"github.com/sirupsen/logrus"
2020-09-16 15:46:04 +02:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/provider"
"github.com/traefik/traefik/v2/pkg/safe"
2021-07-13 14:14:35 +02:00
"github.com/traefik/traefik/v2/pkg/tls"
2019-11-14 16:40:05 +01:00
)
// ConfigurationWatcher watches configuration changes.
type ConfigurationWatcher struct {
2022-02-07 11:58:04 +01:00
providerAggregator provider . Provider
2019-11-14 16:40:05 +01:00
2020-03-09 11:12:05 +01:00
defaultEntryPoints [ ] string
2020-03-05 12:46:05 +01:00
2022-02-07 11:58:04 +01:00
allProvidersConfigs chan dynamic . Message
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
newConfigs chan dynamic . Configurations
2019-11-14 16:40:05 +01:00
2021-02-25 17:20:04 +01:00
requiredProvider string
2019-11-14 16:40:05 +01:00
configurationListeners [ ] func ( dynamic . Configuration )
routinesPool * safe . Pool
}
// NewConfigurationWatcher creates a new ConfigurationWatcher.
2020-03-05 12:46:05 +01:00
func NewConfigurationWatcher (
routinesPool * safe . Pool ,
pvd provider . Provider ,
2020-03-09 11:12:05 +01:00
defaultEntryPoints [ ] string ,
2021-02-25 17:20:04 +01:00
requiredProvider string ,
2020-03-05 12:46:05 +01:00
) * ConfigurationWatcher {
2022-02-07 11:58:04 +01:00
return & ConfigurationWatcher {
providerAggregator : pvd ,
allProvidersConfigs : make ( chan dynamic . Message , 100 ) ,
newConfigs : make ( chan dynamic . Configurations ) ,
routinesPool : routinesPool ,
defaultEntryPoints : defaultEntryPoints ,
requiredProvider : requiredProvider ,
2019-11-14 16:40:05 +01:00
}
}
// Start the configuration watcher.
func ( c * ConfigurationWatcher ) Start ( ) {
2022-02-07 11:58:04 +01:00
c . routinesPool . GoCtx ( c . receiveConfigurations )
c . routinesPool . GoCtx ( c . applyConfigurations )
c . startProviderAggregator ( )
2019-11-14 16:40:05 +01:00
}
// Stop the configuration watcher.
func ( c * ConfigurationWatcher ) Stop ( ) {
2022-02-07 11:58:04 +01:00
close ( c . allProvidersConfigs )
close ( c . newConfigs )
2019-11-14 16:40:05 +01:00
}
2020-05-11 12:06:07 +02:00
// AddListener adds a new listener function used when new configuration is provided.
2019-11-14 16:40:05 +01:00
func ( c * ConfigurationWatcher ) AddListener ( listener func ( dynamic . Configuration ) ) {
if c . configurationListeners == nil {
c . configurationListeners = make ( [ ] func ( dynamic . Configuration ) , 0 )
}
c . configurationListeners = append ( c . configurationListeners , listener )
}
2022-02-07 11:58:04 +01:00
func ( c * ConfigurationWatcher ) startProviderAggregator ( ) {
2019-11-14 16:40:05 +01:00
logger := log . WithoutContext ( )
2022-02-07 11:58:04 +01:00
logger . Infof ( "Starting provider aggregator %T" , c . providerAggregator )
2019-11-14 16:40:05 +01:00
safe . Go ( func ( ) {
2022-02-07 11:58:04 +01:00
err := c . providerAggregator . Provide ( c . allProvidersConfigs , c . routinesPool )
2019-11-14 16:40:05 +01:00
if err != nil {
2022-02-07 11:58:04 +01:00
logger . Errorf ( "Error starting provider aggregator %T: %s" , c . providerAggregator , err )
2019-11-14 16:40:05 +01:00
}
} )
}
2022-02-07 11:58:04 +01:00
// receiveConfigurations receives configuration changes from the providers.
// The configuration message then gets passed along a series of check, notably
// to verify that, for a given provider, the configuration that was just received
// is at least different from the previously received one.
// The full set of configurations is then sent to the throttling goroutine,
// (throttleAndApplyConfigurations) via a RingChannel, which ensures that we can
// constantly send in a non-blocking way to the throttling goroutine the last
// global state we are aware of.
func ( c * ConfigurationWatcher ) receiveConfigurations ( ctx context . Context ) {
newConfigurations := make ( dynamic . Configurations )
var output chan dynamic . Configurations
2019-11-14 16:40:05 +01:00
for {
select {
2020-02-03 17:56:04 +01:00
case <- ctx . Done ( ) :
2019-11-14 16:40:05 +01:00
return
2022-02-07 11:58:04 +01:00
// DeepCopy is necessary because newConfigurations gets modified later by the consumer of c.newConfigs
case output <- newConfigurations . DeepCopy ( ) :
output = nil
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
default :
select {
case <- ctx . Done ( ) :
2019-11-14 16:40:05 +01:00
return
2022-02-07 11:58:04 +01:00
case configMsg , ok := <- c . allProvidersConfigs :
if ! ok {
return
}
logger := log . WithoutContext ( ) . WithField ( log . ProviderName , configMsg . ProviderName )
if configMsg . Configuration == nil {
logger . Debug ( "Skipping nil configuration." )
continue
}
if isEmptyConfiguration ( configMsg . Configuration ) {
logger . Debug ( "Skipping empty configuration." )
continue
}
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
logConfiguration ( logger , configMsg )
if reflect . DeepEqual ( newConfigurations [ configMsg . ProviderName ] , configMsg . Configuration ) {
// no change, do nothing
logger . Debug ( "Skipping unchanged configuration." )
continue
}
newConfigurations [ configMsg . ProviderName ] = configMsg . Configuration . DeepCopy ( )
output = c . newConfigs
// DeepCopy is necessary because newConfigurations gets modified later by the consumer of c.newConfigs
case output <- newConfigurations . DeepCopy ( ) :
output = nil
}
2019-11-14 16:40:05 +01:00
}
}
}
2022-02-07 11:58:04 +01:00
// applyConfigurations blocks on a RingChannel that receives the new
// set of configurations that is compiled and sent by receiveConfigurations as soon
// as a provider change occurs. If the new set is different from the previous set
// that had been applied, the new set is applied, and we sleep for a while before
// listening on the channel again.
func ( c * ConfigurationWatcher ) applyConfigurations ( ctx context . Context ) {
var lastConfigurations dynamic . Configurations
2019-11-14 16:40:05 +01:00
for {
select {
2020-02-03 17:56:04 +01:00
case <- ctx . Done ( ) :
2019-11-14 16:40:05 +01:00
return
2022-02-07 11:58:04 +01:00
case newConfigs , ok := <- c . newConfigs :
if ! ok {
2019-11-14 16:40:05 +01:00
return
}
2022-02-07 11:58:04 +01:00
// We wait for first configuration of the required provider before applying configurations.
if _ , ok := newConfigs [ c . requiredProvider ] ; c . requiredProvider != "" && ! ok {
continue
}
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
if reflect . DeepEqual ( newConfigs , lastConfigurations ) {
continue
}
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
conf := mergeConfiguration ( newConfigs . DeepCopy ( ) , c . defaultEntryPoints )
conf = applyModel ( conf )
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
for _ , listener := range c . configurationListeners {
listener ( conf )
}
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
lastConfigurations = newConfigs
2021-02-25 17:20:04 +01:00
}
2019-11-14 16:40:05 +01:00
}
}
2022-02-07 11:58:04 +01:00
func logConfiguration ( logger log . Logger , configMsg dynamic . Message ) {
if log . GetLevel ( ) != logrus . DebugLevel {
return
}
2021-07-13 14:14:35 +02:00
2022-02-07 11:58:04 +01:00
copyConf := configMsg . Configuration . DeepCopy ( )
if copyConf . TLS != nil {
copyConf . TLS . Certificates = nil
2021-07-13 14:14:35 +02:00
2022-02-07 11:58:04 +01:00
if copyConf . TLS . Options != nil {
cleanedOptions := make ( map [ string ] tls . Options , len ( copyConf . TLS . Options ) )
for name , option := range copyConf . TLS . Options {
option . ClientAuth . CAFiles = [ ] tls . FileOrContent { }
cleanedOptions [ name ] = option
2019-11-14 16:40:05 +01:00
}
2022-02-07 11:58:04 +01:00
copyConf . TLS . Options = cleanedOptions
2021-07-13 14:14:35 +02:00
}
2022-02-07 11:58:04 +01:00
for k := range copyConf . TLS . Stores {
st := copyConf . TLS . Stores [ k ]
st . DefaultCertificate = nil
copyConf . TLS . Stores [ k ] = st
2019-11-14 16:40:05 +01:00
}
}
2022-02-07 11:58:04 +01:00
if copyConf . HTTP != nil {
for _ , transport := range copyConf . HTTP . ServersTransports {
transport . Certificates = tls . Certificates { }
transport . RootCAs = [ ] tls . FileOrContent { }
2019-11-14 16:40:05 +01:00
}
2022-02-07 11:58:04 +01:00
}
2019-11-14 16:40:05 +01:00
2022-02-07 11:58:04 +01:00
jsonConf , err := json . Marshal ( copyConf )
if err != nil {
logger . Errorf ( "Could not marshal dynamic configuration: %v" , err )
logger . Debugf ( "Configuration received: [struct] %#v" , copyConf )
} else {
logger . Debugf ( "Configuration received: %s" , string ( jsonConf ) )
2019-11-14 16:40:05 +01:00
}
}
func isEmptyConfiguration ( conf * dynamic . Configuration ) bool {
if conf . TCP == nil {
conf . TCP = & dynamic . TCPConfiguration { }
}
if conf . HTTP == nil {
conf . HTTP = & dynamic . HTTPConfiguration { }
}
2020-04-16 16:18:04 +02:00
if conf . UDP == nil {
conf . UDP = & dynamic . UDPConfiguration { }
}
2019-11-14 16:40:05 +01:00
httpEmpty := conf . HTTP . Routers == nil && conf . HTTP . Services == nil && conf . HTTP . Middlewares == nil
tlsEmpty := conf . TLS == nil || conf . TLS . Certificates == nil && conf . TLS . Stores == nil && conf . TLS . Options == nil
2021-06-11 15:30:05 +02:00
tcpEmpty := conf . TCP . Routers == nil && conf . TCP . Services == nil && conf . TCP . Middlewares == nil
2020-04-16 16:18:04 +02:00
udpEmpty := conf . UDP . Routers == nil && conf . UDP . Services == nil
2019-11-14 16:40:05 +01:00
2020-04-16 16:18:04 +02:00
return httpEmpty && tlsEmpty && tcpEmpty && udpEmpty
2019-11-14 16:40:05 +01:00
}