Add ACME store
Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
parent
bea5ad3f13
commit
a42845502e
30 changed files with 781 additions and 374 deletions
176
acme/account.go
Normal file
176
acme/account.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
ChallengeCerts map[string][]byte
|
||||
}
|
||||
|
||||
// Init inits acccount struct
|
||||
func (a *Account) Init() error {
|
||||
err := a.DomainsCertificate.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAccount(email string) (*Account, error) {
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||
domainsCerts.Init()
|
||||
return &Account{
|
||||
Email: email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
DomainsCertificate: domainsCerts,
|
||||
ChallengeCerts: map[string][]byte{}}, nil
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a Account) GetRegistration() *acme.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Init() error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
domains := []string{}
|
||||
domains = append(domains, domainsCertificate.Domains.Main)
|
||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||
for _, domain := range domains {
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificate) needRenew() bool {
|
||||
for _, c := range dc.tlsCert.Certificate {
|
||||
crt, err := x509.ParseCertificate(c)
|
||||
if err != nil {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
// <= 7 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
508
acme/acme.go
508
acme/acme.go
|
@ -1,179 +1,36 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"golang.org/x/net/context"
|
||||
"io/ioutil"
|
||||
fmtlog "log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a Account) GetRegistration() *acme.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) init() error {
|
||||
if dc.lock == nil {
|
||||
dc.lock = &sync.RWMutex{}
|
||||
}
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
domains := []string{}
|
||||
domains = append(domains, domainsCertificate.Domains.Main)
|
||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||
for _, domain := range domains {
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificate) needRenew() bool {
|
||||
for _, c := range dc.tlsCert.Certificate {
|
||||
crt, err := x509.ParseCertificate(c)
|
||||
if err != nil {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
// <= 30 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ACME allows to connect to lets encrypt and retrieve certs
|
||||
type ACME struct {
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
StorageFile string `description:"File used for certificates storage."`
|
||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
storageLock sync.RWMutex
|
||||
client *acme.Client
|
||||
account *Account
|
||||
defaultCertificate *tls.Certificate
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
Storage string `description:"File or key used for certificates storage."`
|
||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
client *acme.Client
|
||||
defaultCertificate *tls.Certificate
|
||||
store cluster.Store
|
||||
challengeProvider *challengeProvider
|
||||
checkOnDemandDomain func(domain string) bool
|
||||
}
|
||||
|
||||
//Domains parse []Domain
|
||||
|
@ -218,11 +75,7 @@ type Domain struct {
|
|||
}
|
||||
|
||||
func (a *ACME) init() error {
|
||||
if len(a.Store) == 0 {
|
||||
a.Store = a.StorageFile
|
||||
}
|
||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
log.Debugf("Generating default certificate...")
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generateDefaultCertificate()
|
||||
if err != nil {
|
||||
|
@ -232,78 +85,174 @@ func (a *ACME) init() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CreateClusterConfig creates a tls.config from using ACME configuration
|
||||
func (a *ACME) CreateClusterConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
||||
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Store) == 0 {
|
||||
return errors.New("Empty Store, please provide a filename/key for certs storage")
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("Empty Store, please provide a key for certs storage")
|
||||
}
|
||||
a.checkOnDemandDomain = checkOnDemandDomain
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
|
||||
datastore, err := cluster.NewDataStore(
|
||||
staert.KvSource{
|
||||
Store: leadership.Store,
|
||||
Prefix: leadership.Store.Prefix + "/acme/account",
|
||||
},
|
||||
leadership.Pool.Ctx(), &Account{},
|
||||
func(object cluster.Object) error {
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
if !leadership.IsLeader() {
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.store = datastore
|
||||
a.challengeProvider = newMemoryChallengeProvider(a.store)
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||
log.Infof("Starting ACME renew job...")
|
||||
defer log.Infof("Stopped ACME renew job...")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate: %s", err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
leadership.AddListener(func(elected bool) error {
|
||||
if elected {
|
||||
object, err := a.store.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
var needRegister bool
|
||||
if account == nil || len(account.Email) == 0 {
|
||||
account, err = NewAccount(a.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRegister = true
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("buildACMEClient...")
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Debugf("Register...")
|
||||
reg, err := a.client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
}
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Debugf("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safe.Go(func() {
|
||||
a.retrieveCertificates()
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLocalConfig creates a tls.config from using ACME configuration
|
||||
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
||||
// CreateLocalConfig creates a tls.config using local ACME configuration
|
||||
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Store) == 0 {
|
||||
return errors.New("Empty Store, please provide a filename/key for certs storage")
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("Empty Store, please provide a filename for certs storage")
|
||||
}
|
||||
a.checkOnDemandDomain = checkOnDemandDomain
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
|
||||
localStore := NewLocalStore(a.Storage)
|
||||
a.store = localStore
|
||||
a.challengeProvider = newMemoryChallengeProvider(a.store)
|
||||
|
||||
var needRegister bool
|
||||
var err error
|
||||
var account *Account
|
||||
|
||||
// if certificates in storage, load them
|
||||
if fileInfo, fileErr := os.Stat(a.Store); fileErr == nil && fileInfo.Size() != 0 {
|
||||
log.Infof("Loading ACME certificates...")
|
||||
if fileInfo, fileErr := os.Stat(a.Storage); fileErr == nil && fileInfo.Size() != 0 {
|
||||
log.Infof("Loading ACME Account...")
|
||||
// load account
|
||||
a.account, err = a.loadAccount(a)
|
||||
object, err := localStore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
} else {
|
||||
log.Infof("Generating ACME Account...")
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
account, err = NewAccount(a.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.account = &Account{
|
||||
Email: a.Email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
}
|
||||
a.account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
a.client, err = a.buildACMEClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||
wrapperChallengeProvider := newWrapperChallengeProvider()
|
||||
err = client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
|
||||
log.Infof("buildACMEClient...")
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Infof("Register...")
|
||||
reg, err := a.client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.account.Registration = reg
|
||||
account.Registration = reg
|
||||
}
|
||||
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Infof("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
|
@ -311,45 +260,33 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, CheckOnDemandDomain func
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.account.Registration = reg
|
||||
account.Registration = reg
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", a.account, err.Error())
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
// save account
|
||||
err = a.saveAccount()
|
||||
transaction, _, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
safe.Go(func() {
|
||||
a.retrieveCertificates(a.client)
|
||||
if err := a.renewCertificates(a.client); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", a.account, err.Error())
|
||||
a.retrieveCertificates()
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := a.account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for range ticker.C {
|
||||
if err := a.renewCertificates(client, account); err != nil {
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -358,26 +295,54 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, CheckOnDemandDomain func
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates(client *acme.Client) {
|
||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
account := a.store.Get().(*Account)
|
||||
if challengeCert, ok := a.challengeProvider.getCertificate(clientHello.ServerName); ok {
|
||||
log.Debugf("ACME got challenge %s", clientHello.ServerName)
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
log.Debugf("ACME got domaincert %s", clientHello.ServerName)
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(clientHello.ServerName) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
log.Debugf("ACME got nothing %s", clientHello.ServerName)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates() {
|
||||
log.Infof("Retrieving ACME certificates...")
|
||||
for _, domain := range a.Domains {
|
||||
// check if cert isn't already loaded
|
||||
if _, exists := a.account.DomainsCertificate.exists(domain); !exists {
|
||||
account := a.store.Get().(*Account)
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
domains := []string{}
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
certificateResource, err := a.getDomainsCertificates(client, domains)
|
||||
certificateResource, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
_, err = a.account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
|
||||
continue
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
if err = a.saveAccount(); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", a.account, err.Error())
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -385,12 +350,18 @@ func (a *ACME) retrieveCertificates(client *acme.Client) {
|
|||
log.Infof("Retrieved ACME certificates")
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates(client *acme.Client) error {
|
||||
func (a *ACME) renewCertificates() error {
|
||||
log.Debugf("Testing certificate renew...")
|
||||
for _, certificateResource := range a.account.DomainsCertificate.Certs {
|
||||
account := a.store.Get().(*Account)
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
|
||||
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
|
||||
Domain: certificateResource.Certificate.Domain,
|
||||
CertURL: certificateResource.Certificate.CertURL,
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
|
@ -409,13 +380,14 @@ func (a *ACME) renewCertificates(client *acme.Client) error {
|
|||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}
|
||||
err = a.account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = a.saveAccount(); err != nil {
|
||||
log.Errorf("Error saving ACME account: %v", err)
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -423,33 +395,45 @@ func (a *ACME) renewCertificates(client *acme.Client) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) buildACMEClient() (*acme.Client, error) {
|
||||
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||
log.Debugf("Building ACME client...")
|
||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||
if len(a.CAServer) > 0 {
|
||||
caServer = a.CAServer
|
||||
}
|
||||
client, err := acme.NewClient(caServer, a.account, acme.RSA4096)
|
||||
client, err := acme.NewClient(caServer, account, acme.RSA4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if certificateResource, ok := a.account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
account := a.store.Get().(*Account)
|
||||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
return certificateResource.tlsCert, nil
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates(a.client, []string{clientHello.ServerName})
|
||||
certificate, err := a.getDomainsCertificates([]string{clientHello.ServerName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
||||
cert, err := a.account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName})
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.saveAccount(); err != nil {
|
||||
account = object.(*Account)
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cert.tlsCert, nil
|
||||
|
@ -458,6 +442,7 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C
|
|||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
safe.Go(func() {
|
||||
account := a.store.Get().(*Account)
|
||||
var domain Domain
|
||||
if len(domains) == 0 {
|
||||
// no domain
|
||||
|
@ -468,64 +453,39 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
|||
} else {
|
||||
domain = Domain{Main: domains[0]}
|
||||
}
|
||||
if _, exists := a.account.DomainsCertificate.exists(domain); exists {
|
||||
if _, exists := account.DomainsCertificate.exists(domain); exists {
|
||||
// domain already exists
|
||||
return
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates(a.client, domains)
|
||||
certificate, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("Got certificate for domains %+v", domains)
|
||||
_, err = a.account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||
transaction, object, err := a.store.Begin()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating transaction %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
if err = a.saveAccount(); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %v", a.account, err)
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
|
||||
a.storageLock.RLock()
|
||||
defer a.storageLock.RUnlock()
|
||||
account := Account{
|
||||
DomainsCertificate: DomainsCertificates{},
|
||||
}
|
||||
file, err := ioutil.ReadFile(acmeConfig.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(file, &account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = account.DomainsCertificate.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Loaded ACME config from store %s", acmeConfig.Store)
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func (a *ACME) saveAccount() error {
|
||||
a.storageLock.Lock()
|
||||
defer a.storageLock.Unlock()
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(a.account, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(a.Store, data, 0600)
|
||||
}
|
||||
|
||||
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
|
||||
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := true
|
||||
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
|
||||
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||
|
|
|
@ -4,33 +4,59 @@ import (
|
|||
"crypto/tls"
|
||||
"sync"
|
||||
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/gob"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ acme.ChallengeProvider = (*inMemoryChallengeProvider)(nil)
|
||||
|
||||
type inMemoryChallengeProvider struct {
|
||||
challengeCerts map[string]*tls.Certificate
|
||||
lock sync.RWMutex
|
||||
func init() {
|
||||
gob.Register(rsa.PrivateKey{})
|
||||
gob.Register(rsa.PublicKey{})
|
||||
}
|
||||
|
||||
func newWrapperChallengeProvider() *inMemoryChallengeProvider {
|
||||
return &inMemoryChallengeProvider{
|
||||
challengeCerts: map[string]*tls.Certificate{},
|
||||
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
||||
|
||||
type challengeProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newMemoryChallengeProvider(store cluster.Store) *challengeProvider {
|
||||
return &challengeProvider{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *inMemoryChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
log.Debugf("Challenge GetCertificate %s", domain)
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
if cert, ok := c.challengeCerts[domain]; ok {
|
||||
account := c.store.Get().(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
return nil, false
|
||||
}
|
||||
if certBinary, ok := account.ChallengeCerts[domain]; ok {
|
||||
cert := &tls.Certificate{}
|
||||
var buffer bytes.Buffer
|
||||
buffer.Write(certBinary)
|
||||
dec := gob.NewDecoder(&buffer)
|
||||
err := dec.Decode(cert)
|
||||
if err != nil {
|
||||
log.Errorf("Error unmarshaling challenge cert %s", err.Error())
|
||||
return nil, false
|
||||
}
|
||||
return cert, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *inMemoryChallengeProvider) Present(domain, token, keyAuth string) error {
|
||||
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge Present %s", domain)
|
||||
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -42,16 +68,40 @@ func (c *inMemoryChallengeProvider) Present(domain, token, keyAuth string) error
|
|||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
for i := range cert.Leaf.DNSNames {
|
||||
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
account := object.(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
account.ChallengeCerts = map[string][]byte{}
|
||||
}
|
||||
for i := range cert.Leaf.DNSNames {
|
||||
var buffer bytes.Buffer
|
||||
enc := gob.NewEncoder(&buffer)
|
||||
err := enc.Encode(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.ChallengeCerts[cert.Leaf.DNSNames[i]] = buffer.Bytes()
|
||||
log.Debugf("Challenge Present cert: %s", cert.Leaf.DNSNames[i])
|
||||
}
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *inMemoryChallengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
delete(c.challengeCerts, domain)
|
||||
return nil
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
delete(account.ChallengeCerts, domain)
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
||||
|
|
97
acme/localStore.go
Normal file
97
acme/localStore.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ cluster.Store = (*LocalStore)(nil)
|
||||
|
||||
// LocalStore is a store using a file as storage
|
||||
type LocalStore struct {
|
||||
file string
|
||||
storageLock sync.RWMutex
|
||||
account *Account
|
||||
}
|
||||
|
||||
// NewLocalStore create a LocalStore
|
||||
func NewLocalStore(file string) *LocalStore {
|
||||
return &LocalStore{
|
||||
file: file,
|
||||
storageLock: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get atomically a struct from the file storage
|
||||
func (s *LocalStore) Get() cluster.Object {
|
||||
s.storageLock.RLock()
|
||||
defer s.storageLock.RUnlock()
|
||||
return s.account
|
||||
}
|
||||
|
||||
// Load loads file into store
|
||||
func (s *LocalStore) Load() (cluster.Object, error) {
|
||||
s.storageLock.Lock()
|
||||
defer s.storageLock.Unlock()
|
||||
account := &Account{}
|
||||
file, err := ioutil.ReadFile(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(file, &account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account.Init()
|
||||
s.account = account
|
||||
log.Infof("Loaded ACME config from store %s", s.file)
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// func (s *LocalStore) saveAccount(account *Account) error {
|
||||
// s.storageLock.Lock()
|
||||
// defer s.storageLock.Unlock()
|
||||
// // write account to file
|
||||
// data, err := json.MarshalIndent(account, "", " ")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return ioutil.WriteFile(s.file, data, 0644)
|
||||
// }
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (s *LocalStore) Begin() (cluster.Transaction, cluster.Object, error) {
|
||||
s.storageLock.Lock()
|
||||
return &localTransaction{LocalStore: s}, s.account, nil
|
||||
}
|
||||
|
||||
var _ cluster.Transaction = (*localTransaction)(nil)
|
||||
|
||||
type localTransaction struct {
|
||||
*LocalStore
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the file storage
|
||||
func (t *localTransaction) Commit(object cluster.Object) error {
|
||||
t.LocalStore.account = object.(*Account)
|
||||
defer t.storageLock.Unlock()
|
||||
if t.dirty {
|
||||
return fmt.Errorf("Transaction already used. Please begin a new one.")
|
||||
}
|
||||
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(object, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(t.file, data, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.dirty = true
|
||||
return nil
|
||||
}
|
|
@ -6,7 +6,7 @@ package main
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
)
|
||||
|
||||
// OxyLogger implements oxy Logger interface with logrus.
|
||||
|
|
|
@ -1,44 +1,62 @@
|
|||
package cluster
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/emilevauge/backoff"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Object is the struct to store
|
||||
type Object interface{}
|
||||
|
||||
// Metadata stores Object plus metadata
|
||||
type Metadata struct {
|
||||
Lock string
|
||||
object Object
|
||||
Object []byte
|
||||
Lock string
|
||||
}
|
||||
|
||||
func (m *Metadata) marshall() error {
|
||||
var err error
|
||||
m.Object, err = json.Marshal(m.object)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Metadata) unmarshall() error {
|
||||
if len(m.Object) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(m.Object, m.object)
|
||||
}
|
||||
|
||||
// Listener is called when Object has been changed in KV store
|
||||
type Listener func(Object) error
|
||||
|
||||
var _ Store = (*Datastore)(nil)
|
||||
|
||||
// Datastore holds a struct synced in a KV store
|
||||
type Datastore struct {
|
||||
kv *staert.KvSource
|
||||
kv staert.KvSource
|
||||
ctx context.Context
|
||||
localLock *sync.RWMutex
|
||||
object Object
|
||||
meta *Metadata
|
||||
lockKey string
|
||||
listener Listener
|
||||
}
|
||||
|
||||
// NewDataStore creates a Datastore
|
||||
func NewDataStore(kvSource *staert.KvSource, ctx context.Context, object Object) (*Datastore, error) {
|
||||
func NewDataStore(kvSource staert.KvSource, ctx context.Context, object Object, listener Listener) (*Datastore, error) {
|
||||
datastore := Datastore{
|
||||
kv: kvSource,
|
||||
ctx: ctx,
|
||||
meta: &Metadata{},
|
||||
object: object,
|
||||
meta: &Metadata{object: object},
|
||||
lockKey: kvSource.Prefix + "/lock",
|
||||
localLock: &sync.RWMutex{},
|
||||
listener: listener,
|
||||
}
|
||||
err := datastore.watchChanges()
|
||||
if err != nil {
|
||||
|
@ -67,17 +85,24 @@ func (d *Datastore) watchChanges() error {
|
|||
return err
|
||||
}
|
||||
d.localLock.Lock()
|
||||
err := d.kv.LoadConfig(d.object)
|
||||
err := d.kv.LoadConfig(d.meta)
|
||||
if err != nil {
|
||||
d.localLock.Unlock()
|
||||
return err
|
||||
}
|
||||
err = d.kv.LoadConfig(d.meta)
|
||||
err = d.meta.unmarshall()
|
||||
if err != nil {
|
||||
d.localLock.Unlock()
|
||||
return err
|
||||
}
|
||||
d.localLock.Unlock()
|
||||
// log.Debugf("Datastore object change received: %+v", d.object)
|
||||
if d.listener != nil {
|
||||
err := d.listener(d.meta.object)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling datastore listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,11 +118,12 @@ func (d *Datastore) watchChanges() error {
|
|||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (d *Datastore) Begin() (*Transaction, error) {
|
||||
func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||
id := uuid.NewV4().String()
|
||||
log.Debugf("Transaction %s begins", id)
|
||||
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
|
@ -109,11 +135,11 @@ func (d *Datastore) Begin() (*Transaction, error) {
|
|||
select {
|
||||
case <-ctx.Done():
|
||||
if errLock != nil {
|
||||
return nil, errLock
|
||||
return nil, nil, errLock
|
||||
}
|
||||
case <-d.ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil, d.ctx.Err()
|
||||
return nil, nil, d.ctx.Err()
|
||||
}
|
||||
|
||||
// we got the lock! Now make sure we are synced with KV store
|
||||
|
@ -131,14 +157,15 @@ func (d *Datastore) Begin() (*Transaction, error) {
|
|||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(operation, ebo, notify)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||
}
|
||||
|
||||
// we synced with KV store, we can now return Setter
|
||||
return &Transaction{
|
||||
return &datastoreTransaction{
|
||||
Datastore: d,
|
||||
remoteLock: remoteLock,
|
||||
}, nil
|
||||
id: id,
|
||||
}, d.meta.object, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) get() *Metadata {
|
||||
|
@ -147,28 +174,50 @@ func (d *Datastore) get() *Metadata {
|
|||
return d.meta
|
||||
}
|
||||
|
||||
// Load load atomically a struct from the KV store
|
||||
func (d *Datastore) Load() (Object, error) {
|
||||
d.localLock.Lock()
|
||||
defer d.localLock.Unlock()
|
||||
err := d.kv.LoadConfig(d.meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.meta.unmarshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.meta.object, nil
|
||||
}
|
||||
|
||||
// Get atomically a struct from the KV store
|
||||
func (d *Datastore) Get() Object {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.object
|
||||
return d.meta.object
|
||||
}
|
||||
|
||||
// Transaction allows to set a struct in the KV store
|
||||
type Transaction struct {
|
||||
var _ Transaction = (*datastoreTransaction)(nil)
|
||||
|
||||
type datastoreTransaction struct {
|
||||
*Datastore
|
||||
remoteLock store.Locker
|
||||
dirty bool
|
||||
id string
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the KV store
|
||||
func (s *Transaction) Commit(object Object) error {
|
||||
func (s *datastoreTransaction) Commit(object Object) error {
|
||||
s.localLock.Lock()
|
||||
defer s.localLock.Unlock()
|
||||
if s.dirty {
|
||||
return fmt.Errorf("Transaction already used. Please begin a new one.")
|
||||
}
|
||||
err := s.kv.StoreConfig(object)
|
||||
s.Datastore.meta.object = object
|
||||
err := s.Datastore.meta.marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -178,7 +227,8 @@ func (s *Transaction) Commit(object Object) error {
|
|||
return err
|
||||
}
|
||||
|
||||
s.Datastore.object = object
|
||||
s.dirty = true
|
||||
// log.Debugf("Datastore object saved: %+v", s.object)
|
||||
log.Debugf("Transaction commited %s", s.id)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package cluster
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/leadership"
|
||||
"github.com/emilevauge/backoff"
|
||||
"golang.org/x/net/context"
|
||||
"time"
|
||||
)
|
||||
|
@ -15,6 +15,8 @@ type Leadership struct {
|
|||
*safe.Pool
|
||||
*types.Cluster
|
||||
candidate *leadership.Candidate
|
||||
leader safe.Safe
|
||||
listeners []LeaderListener
|
||||
}
|
||||
|
||||
// NewLeadership creates a leadership
|
||||
|
@ -23,9 +25,13 @@ func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
|||
Pool: safe.NewPool(ctx),
|
||||
Cluster: cluster,
|
||||
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
||||
listeners: []LeaderListener{},
|
||||
}
|
||||
}
|
||||
|
||||
// LeaderListener is called when leadership has changed
|
||||
type LeaderListener func(elected bool) error
|
||||
|
||||
// Participate tries to be a leader
|
||||
func (l *Leadership) Participate(pool *safe.Pool) {
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
|
@ -46,10 +52,15 @@ func (l *Leadership) Participate(pool *safe.Pool) {
|
|||
})
|
||||
}
|
||||
|
||||
// AddListener adds a leadership listerner
|
||||
func (l *Leadership) AddListener(listener LeaderListener) {
|
||||
l.listeners = append(l.listeners, listener)
|
||||
}
|
||||
|
||||
// Resign resigns from being a leader
|
||||
func (l *Leadership) Resign() {
|
||||
l.candidate.Resign()
|
||||
log.Infof("Node %s resined", l.Cluster.Node)
|
||||
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||
}
|
||||
|
||||
func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) error {
|
||||
|
@ -70,9 +81,22 @@ func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) e
|
|||
func (l *Leadership) onElection(elected bool) {
|
||||
if elected {
|
||||
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
||||
l.leader.Set(true)
|
||||
l.Start()
|
||||
} else {
|
||||
log.Infof("Node %s elected slave ♝", l.Cluster.Node)
|
||||
l.leader.Set(false)
|
||||
l.Stop()
|
||||
}
|
||||
for _, listener := range l.listeners {
|
||||
err := listener(elected)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling Leadership listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeader returns true if current node is leader
|
||||
func (l *Leadership) IsLeader() bool {
|
||||
return l.leader.Get().(bool)
|
||||
}
|
||||
|
|
16
cluster/store.go
Normal file
16
cluster/store.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package cluster
|
||||
|
||||
// Object is the struct to store
|
||||
type Object interface{}
|
||||
|
||||
// Store is a generic interface to represents a storage
|
||||
type Store interface {
|
||||
Load() (Object, error)
|
||||
Get() Object
|
||||
Begin() (Transaction, Object, error)
|
||||
}
|
||||
|
||||
// Transaction allows to set a struct in the KV store
|
||||
type Transaction interface {
|
||||
Commit(object Object) error
|
||||
}
|
|
@ -23,14 +23,14 @@ type TraefikConfiguration struct {
|
|||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
|
||||
Debug bool `short:"d" description:"Enable debug mode"`
|
||||
AccessLogsFile string `description:"Access logs file"`
|
||||
TraefikLogsFile string `description:"Traefik logs file"`
|
||||
LogLevel string `short:"l" description:"Log level"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||
Cluster *types.Cluster
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
|
||||
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
|
||||
Debug bool `short:"d" description:"Enable debug mode"`
|
||||
AccessLogsFile string `description:"Access logs file"`
|
||||
TraefikLogsFile string `description:"Traefik logs file"`
|
||||
LogLevel string `short:"l" description:"Log level"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||
Cluster *types.Cluster `description:"Enable clustering"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||
|
|
10
glide.lock
generated
10
glide.lock
generated
|
@ -1,3 +1,4 @@
|
|||
<<<<<<< 2fbcca003e6454c848801c859d8563da94ea8aaf
|
||||
<<<<<<< a13549cc28273ba5c15a739fa4aaeb3e0f7216a4
|
||||
hash: c0ac205a859d78847e21d3cd63f427ffba985755c6ae84373e4a20364ba39b05
|
||||
<<<<<<< 38b62d4ae311e2d5247065cbc2c09421a2bb81ab
|
||||
|
@ -8,7 +9,14 @@ updated: 2016-09-28T16:50:04.352639437+01:00
|
|||
hash: 809b3fa812ca88940fdc15530804a4bcd881708e4819fed5aa45c42c871ba5cf
|
||||
updated: 2016-09-20T14:50:04.029710103+02:00
|
||||
>>>>>>> Add KV datastore
|
||||
<<<<<<< bea5ad3f132bae27b6c1a83adf00154058b484b5
|
||||
>>>>>>> Add KV datastore
|
||||
=======
|
||||
=======
|
||||
hash: 49c7bd0e32b2764248183bda52f168fe22d69e2db5e17c1dbeebbe71be9929b1
|
||||
updated: 2016-08-11T14:33:42.826534934+02:00
|
||||
>>>>>>> Add ACME store
|
||||
>>>>>>> Add ACME store
|
||||
imports:
|
||||
- name: github.com/abbot/go-http-auth
|
||||
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
|
||||
|
@ -33,7 +41,7 @@ imports:
|
|||
- name: github.com/containous/mux
|
||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||
- name: github.com/containous/staert
|
||||
version: 044bdfee6c8f5e8fb71f70d5ba1cf4cb11a94e97
|
||||
version: 56058c7d4152831a641764d10ec91132adf061ea
|
||||
- name: github.com/coreos/etcd
|
||||
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
|
||||
subpackages:
|
||||
|
|
|
@ -21,7 +21,7 @@ import:
|
|||
- stream
|
||||
- utils
|
||||
- package: github.com/containous/staert
|
||||
version: 044bdfee6c8f5e8fb71f70d5ba1cf4cb11a94e97
|
||||
version: 56058c7d4152831a641764d10ec91132adf061ea
|
||||
- package: github.com/docker/engine-api
|
||||
version: 62043eb79d581a32ea849645277023c550732e52
|
||||
subpackages:
|
||||
|
|
|
@ -2,7 +2,7 @@ package middlewares
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/abbot/go-http-auth"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/traefik/types"
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package middlewares
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"net"
|
||||
"net/http"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/vulcand/vulcand/plugin/rewrite"
|
||||
"net/http"
|
||||
)
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/safe"
|
||||
|
@ -270,7 +271,7 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
|||
name := strings.ToLower(service)
|
||||
if !strings.Contains(name, " ") && !visited[name] {
|
||||
visited[name] = true
|
||||
log.WithFields(log.Fields{
|
||||
log.WithFields(logrus.Fields{
|
||||
"service": name,
|
||||
}).Debug("Fetching service")
|
||||
healthy, err := provider.healthyNodes(name)
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/safe"
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
|
|
@ -2,6 +2,10 @@ package provider
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/k8s"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
|
@ -10,7 +14,6 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/provider/k8s"
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"errors"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/safe"
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/safe"
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"fmt"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/safe"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/mesosphere/mesos-dns/records/state"
|
||||
"reflect"
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package safe
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/log"
|
||||
"golang.org/x/net/context"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
)
|
||||
|
@ -26,18 +26,26 @@ type Pool struct {
|
|||
}
|
||||
|
||||
// NewPool creates a Pool
|
||||
func NewPool(baseCtx context.Context) *Pool {
|
||||
func NewPool(parentCtx context.Context) *Pool {
|
||||
baseCtx, _ := context.WithCancel(parentCtx)
|
||||
ctx, cancel := context.WithCancel(baseCtx)
|
||||
return &Pool{
|
||||
baseCtx: baseCtx,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
baseCtx: baseCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// Ctx returns main context
|
||||
func (p *Pool) Ctx() context.Context {
|
||||
return p.ctx
|
||||
return p.baseCtx
|
||||
}
|
||||
|
||||
//AddGoCtx adds a recoverable goroutine with a context without starting it
|
||||
func (p *Pool) AddGoCtx(goroutine routineCtx) {
|
||||
p.lock.Lock()
|
||||
p.routinesCtx = append(p.routinesCtx, goroutine)
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
//GoCtx starts a recoverable goroutine with a context
|
||||
|
@ -71,6 +79,7 @@ func (p *Pool) Go(goroutine func(stop chan bool)) {
|
|||
// Stop stops all started routines, waiting for their termination
|
||||
func (p *Pool) Stop() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
p.cancel()
|
||||
for _, routine := range p.routines {
|
||||
routine.stop <- true
|
||||
|
@ -79,12 +88,12 @@ func (p *Pool) Stop() {
|
|||
for _, routine := range p.routines {
|
||||
close(routine.stop)
|
||||
}
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
// Start starts all stoped routines
|
||||
// Start starts all stopped routines
|
||||
func (p *Pool) Start() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
p.ctx, p.cancel = context.WithCancel(p.baseCtx)
|
||||
for _, routine := range p.routines {
|
||||
p.waitGroup.Add(1)
|
||||
|
@ -102,7 +111,6 @@ func (p *Pool) Start() {
|
|||
p.waitGroup.Done()
|
||||
})
|
||||
}
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
// Go starts a recoverable goroutine
|
||||
|
@ -123,6 +131,6 @@ func GoWithRecover(goroutine func(), customRecover func(err interface{})) {
|
|||
}
|
||||
|
||||
func defaultRecoverGoroutine(err interface{}) {
|
||||
log.Println(err)
|
||||
log.Errorf("Error in Go routine: %s", err)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
|
39
server.go
39
server.go
|
@ -21,10 +21,10 @@ import (
|
|||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/safe"
|
||||
|
@ -93,8 +93,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||
|
||||
// Start starts the server and blocks until server is shutted down.
|
||||
func (server *Server) Start() {
|
||||
server.startLeadership()
|
||||
server.startHTTPServers()
|
||||
server.startLeadership()
|
||||
server.routinesPool.Go(func(stop chan bool) {
|
||||
server.listenProviders(stop)
|
||||
})
|
||||
|
@ -129,7 +129,7 @@ func (server *Server) Close() {
|
|||
if ctx.Err() == context.Canceled {
|
||||
return
|
||||
} else if ctx.Err() == context.DeadlineExceeded {
|
||||
log.Debugf("I love you all :'( ✝")
|
||||
log.Warnf("Timeout while stopping traefik, killing instance ✝")
|
||||
os.Exit(1)
|
||||
}
|
||||
}(ctx)
|
||||
|
@ -147,17 +147,17 @@ func (server *Server) Close() {
|
|||
func (server *Server) startLeadership() {
|
||||
if server.leadership != nil {
|
||||
server.leadership.Participate(server.routinesPool)
|
||||
server.leadership.GoCtx(func(ctx context.Context) {
|
||||
log.Debugf("Started test routine")
|
||||
<-ctx.Done()
|
||||
log.Debugf("Stopped test routine")
|
||||
})
|
||||
// server.leadership.AddGoCtx(func(ctx context.Context) {
|
||||
// log.Debugf("Started test routine")
|
||||
// <-ctx.Done()
|
||||
// log.Debugf("Stopped test routine")
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) stopLeadership() {
|
||||
if server.leadership != nil {
|
||||
server.leadership.Resign()
|
||||
server.leadership.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,7 +283,13 @@ func (server *Server) listenConfigurations(stop chan bool) {
|
|||
}
|
||||
|
||||
func (server *Server) postLoadConfig() {
|
||||
if server.globalConfiguration.ACME != nil && server.globalConfiguration.ACME.OnHostRule {
|
||||
if server.globalConfiguration.ACME == nil {
|
||||
return
|
||||
}
|
||||
if server.leadership != nil && !server.leadership.IsLeader() {
|
||||
return
|
||||
}
|
||||
if server.globalConfiguration.ACME.OnHostRule {
|
||||
currentConfigurations := server.currentConfigurations.Get().(configs)
|
||||
for _, configuration := range currentConfigurations {
|
||||
for _, frontend := range configuration.Frontends {
|
||||
|
@ -401,9 +407,16 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou
|
|||
}
|
||||
return false
|
||||
}
|
||||
err := server.globalConfiguration.ACME.CreateLocalConfig(config, checkOnDemandDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if server.leadership == nil {
|
||||
err := server.globalConfiguration.ACME.CreateLocalConfig(config, checkOnDemandDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := server.globalConfiguration.ACME.CreateClusterConfig(server.leadership, config, checkOnDemandDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -12,10 +12,11 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
|
@ -208,7 +209,7 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
|||
}
|
||||
|
||||
// logging
|
||||
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||
level, err := logrus.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||
if err != nil {
|
||||
log.Error("Error getting level", err)
|
||||
}
|
||||
|
@ -224,10 +225,10 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
|||
log.Error("Error opening file", err)
|
||||
} else {
|
||||
log.SetOutput(fi)
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||
log.SetFormatter(&logrus.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
} else {
|
||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
|
|
|
@ -201,7 +201,7 @@ type Store struct {
|
|||
|
||||
// Cluster holds cluster config
|
||||
type Cluster struct {
|
||||
Node string
|
||||
Node string `description:"Node name"`
|
||||
Store *Store
|
||||
}
|
||||
|
||||
|
|
2
web.go
2
web.go
|
@ -8,7 +8,7 @@ import (
|
|||
"net/http"
|
||||
"runtime"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/autogen"
|
||||
|
|
Loading…
Reference in a new issue