2017-04-17 12:50:02 +02:00
package dynamodb
2017-03-08 18:53:34 -07:00
import (
"context"
"errors"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
2017-04-17 12:50:02 +02:00
"github.com/containous/traefik/provider"
2017-03-08 18:53:34 -07:00
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
)
2017-04-17 12:50:02 +02:00
var _ provider . Provider = ( * Provider ) ( nil )
2017-03-08 18:53:34 -07:00
2017-04-17 12:50:02 +02:00
// Provider holds configuration for provider.
type Provider struct {
2017-10-02 10:32:02 +02:00
provider . BaseProvider ` mapstructure:",squash" export:"true" `
AccessKeyID string ` description:"The AWS credentials access key to use for making requests" `
RefreshSeconds int ` description:"Polling interval (in seconds)" export:"true" `
Region string ` description:"The AWS region to use for requests" export:"true" `
SecretAccessKey string ` description:"The AWS credentials secret key to use for making requests" `
TableName string ` description:"The AWS dynamodb table that stores configuration for traefik" export:"true" `
Endpoint string ` description:"The endpoint of a dynamodb. Used for testing with a local dynamodb" `
2017-03-08 18:53:34 -07:00
}
type dynamoClient struct {
db dynamodbiface . DynamoDBAPI
}
2018-07-11 09:08:03 +02:00
// Init the provider
func ( p * Provider ) Init ( constraints types . Constraints ) error {
return p . BaseProvider . Init ( constraints )
}
2017-03-08 18:53:34 -07:00
// createClient configures aws credentials and creates a dynamoClient
2017-04-17 12:50:02 +02:00
func ( p * Provider ) createClient ( ) ( * dynamoClient , error ) {
2017-05-26 17:03:14 +02:00
log . Info ( "Creating Provider client..." )
2018-02-22 14:58:04 +01:00
sess , err := session . NewSession ( )
if err != nil {
return nil , err
}
2017-04-17 12:50:02 +02:00
if p . Region == "" {
return nil , errors . New ( "no Region provided for Provider" )
2017-03-08 18:53:34 -07:00
}
cfg := & aws . Config {
2017-04-17 12:50:02 +02:00
Region : & p . Region ,
2017-03-08 18:53:34 -07:00
Credentials : credentials . NewChainCredentials (
[ ] credentials . Provider {
& credentials . StaticProvider {
Value : credentials . Value {
2017-04-17 12:50:02 +02:00
AccessKeyID : p . AccessKeyID ,
SecretAccessKey : p . SecretAccessKey ,
2017-03-08 18:53:34 -07:00
} ,
} ,
& credentials . EnvProvider { } ,
& credentials . SharedCredentialsProvider { } ,
defaults . RemoteCredProvider ( * ( defaults . Config ( ) ) , defaults . Handlers ( ) ) ,
} ) ,
}
2017-07-07 23:49:14 +02:00
if p . Trace {
cfg . WithLogger ( aws . LoggerFunc ( func ( args ... interface { } ) {
log . Debug ( args ... )
} ) )
}
2017-04-17 12:50:02 +02:00
if p . Endpoint != "" {
cfg . Endpoint = aws . String ( p . Endpoint )
2017-03-08 18:53:34 -07:00
}
return & dynamoClient {
dynamodb . New ( sess , cfg ) ,
} , nil
}
// scanTable scans the given table and returns slice of all items in the table
2017-04-17 12:50:02 +02:00
func ( p * Provider ) scanTable ( client * dynamoClient ) ( [ ] map [ string ] * dynamodb . AttributeValue , error ) {
log . Debugf ( "Scanning Provider table: %s ..." , p . TableName )
2017-03-08 18:53:34 -07:00
params := & dynamodb . ScanInput {
2017-04-17 12:50:02 +02:00
TableName : aws . String ( p . TableName ) ,
2017-03-08 18:53:34 -07:00
}
items := make ( [ ] map [ string ] * dynamodb . AttributeValue , 0 )
err := client . db . ScanPages ( params ,
func ( page * dynamodb . ScanOutput , lastPage bool ) bool {
items = append ( items , page . Items ... )
return ! lastPage
} )
if err != nil {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Failed to scan Provider table %s" , p . TableName )
2017-03-08 18:53:34 -07:00
return nil , err
}
2017-04-17 12:50:02 +02:00
log . Debugf ( "Successfully scanned Provider table %s" , p . TableName )
2017-03-08 18:53:34 -07:00
return items , nil
}
2017-12-02 19:25:29 +01:00
// buildConfiguration retrieves items from dynamodb and converts them into Backends and Frontends in a Configuration
func ( p * Provider ) buildConfiguration ( client * dynamoClient ) ( * types . Configuration , error ) {
2017-04-17 12:50:02 +02:00
items , err := p . scanTable ( client )
2017-03-08 18:53:34 -07:00
if err != nil {
return nil , err
}
2017-04-17 12:50:02 +02:00
log . Debugf ( "Number of Items retrieved from Provider: %d" , len ( items ) )
2017-03-08 18:53:34 -07:00
backends := make ( map [ string ] * types . Backend )
frontends := make ( map [ string ] * types . Frontend )
// unmarshal dynamoAttributes into Backends and Frontends
for i , item := range items {
2017-04-17 12:50:02 +02:00
log . Debugf ( "Provider Item: %d\n%v" , i , item )
2017-03-08 18:53:34 -07:00
// verify the type of each item by checking to see if it has
// the corresponding type, backend or frontend map
if backend , exists := item [ "backend" ] ; exists {
2017-05-26 17:03:14 +02:00
log . Debug ( "Unmarshaling backend from Provider..." )
2017-03-08 18:53:34 -07:00
tmpBackend := & types . Backend { }
err = dynamodbattribute . Unmarshal ( backend , tmpBackend )
if err != nil {
log . Errorf ( err . Error ( ) )
} else {
backends [ * item [ "name" ] . S ] = tmpBackend
2017-05-26 17:03:14 +02:00
log . Debug ( "Backend from Provider unmarshalled successfully" )
2017-03-08 18:53:34 -07:00
}
} else if frontend , exists := item [ "frontend" ] ; exists {
2017-04-17 12:50:02 +02:00
log . Debugf ( "Unmarshaling frontend from Provider..." )
2017-03-08 18:53:34 -07:00
tmpFrontend := & types . Frontend { }
err = dynamodbattribute . Unmarshal ( frontend , tmpFrontend )
if err != nil {
log . Errorf ( err . Error ( ) )
} else {
frontends [ * item [ "name" ] . S ] = tmpFrontend
2017-05-26 17:03:14 +02:00
log . Debug ( "Frontend from Provider unmarshalled successfully" )
2017-03-08 18:53:34 -07:00
}
} else {
2017-04-17 12:50:02 +02:00
log . Warnf ( "Error in format of Provider Item: %v" , item )
2017-03-08 18:53:34 -07:00
}
}
return & types . Configuration {
Backends : backends ,
Frontends : frontends ,
} , nil
}
// Provide provides the configuration to traefik via the configuration channel
// if watch is enabled it polls dynamodb
2018-07-11 09:08:03 +02:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool ) error {
2017-03-08 18:53:34 -07:00
handleCanceled := func ( ctx context . Context , err error ) error {
if ctx . Err ( ) == context . Canceled || err == context . Canceled {
return nil
}
return err
}
pool . Go ( func ( stop chan bool ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
2017-07-19 14:11:45 +02:00
safe . Go ( func ( ) {
2017-12-06 10:52:03 +01:00
<- stop
cancel ( )
2017-07-19 14:11:45 +02:00
} )
2017-03-08 18:53:34 -07:00
operation := func ( ) error {
2017-10-10 11:10:02 +02:00
awsClient , err := p . createClient ( )
2017-03-08 18:53:34 -07:00
if err != nil {
return handleCanceled ( ctx , err )
}
2017-12-02 19:25:29 +01:00
configuration , err := p . buildConfiguration ( awsClient )
2017-03-08 18:53:34 -07:00
if err != nil {
return handleCanceled ( ctx , err )
}
configurationChan <- types . ConfigMessage {
ProviderName : "dynamodb" ,
Configuration : configuration ,
}
2017-04-17 12:50:02 +02:00
if p . Watch {
reload := time . NewTicker ( time . Second * time . Duration ( p . RefreshSeconds ) )
2017-03-08 18:53:34 -07:00
defer reload . Stop ( )
for {
2017-05-26 17:03:14 +02:00
log . Debug ( "Watching Provider..." )
2017-03-08 18:53:34 -07:00
select {
case <- reload . C :
2017-12-02 19:25:29 +01:00
configuration , err := p . buildConfiguration ( awsClient )
2017-03-08 18:53:34 -07:00
if err != nil {
return handleCanceled ( ctx , err )
}
configurationChan <- types . ConfigMessage {
ProviderName : "dynamodb" ,
Configuration : configuration ,
}
case <- ctx . Done ( ) :
return handleCanceled ( ctx , ctx . Err ( ) )
}
}
}
return nil
}
notify := func ( err error , time time . Duration ) {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Provider error: %s time: %v" , err . Error ( ) , time )
2017-03-08 18:53:34 -07:00
}
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
if err != nil {
2017-04-17 12:50:02 +02:00
log . Errorf ( "Failed to connect to Provider. %s" , err . Error ( ) )
2017-03-08 18:53:34 -07:00
}
} )
return nil
}