diff --git a/pkg/modules/auth/ldap/ldap.go b/pkg/modules/auth/ldap/ldap.go index dbc90b06e..2467a2ed9 100644 --- a/pkg/modules/auth/ldap/ldap.go +++ b/pkg/modules/auth/ldap/ldap.go @@ -61,11 +61,17 @@ func InitializeLDAPConnection() { log.Fatal("LDAP user filter is not configured") } - l, err := ConnectAndBindToLDAPDirectory() + err := utils.RetryWithBackoff("LDAP server", func() error { + l, connErr := ConnectAndBindToLDAPDirectory() + if connErr == nil { + _ = l.Close() + } + return connErr + }) + if err != nil { - log.Fatalf("Could not bind to LDAP server: %s", err) + log.Fatalf("Could not connect to LDAP server: %s", err) } - _ = l.Close() } func ConnectAndBindToLDAPDirectory() (l *ldap.Conn, err error) { @@ -90,7 +96,7 @@ func ConnectAndBindToLDAPDirectory() (l *ldap.Conn, err error) { l, err = ldap.DialURL(url, opts...) if err != nil { - log.Fatalf("Could not connect to LDAP server: %s", err) + return nil, fmt.Errorf("could not connect to LDAP server: %w", err) } err = l.Bind( diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go index 3c8ba9aa9..6f57c7204 100644 --- a/pkg/modules/auth/openid/openid.go +++ b/pkg/modules/auth/openid/openid.go @@ -83,10 +83,16 @@ func init() { } func (p *Provider) setOicdProvider() (err error) { - p.openIDProvider, err = oidc.NewProvider(context.Background(), p.OriginalAuthURL) + err = utils.RetryWithBackoff(fmt.Sprintf("OpenID Connect provider '%s'", p.Name), func() error { + var providerErr error + p.openIDProvider, providerErr = oidc.NewProvider(context.Background(), p.OriginalAuthURL) + return providerErr + }) + if err != nil && p.RequireAvailability { log.Fatalf("OpenID Connect provider '%s' is not available and require_availability is enabled: %s", p.Name, err) } + return err } diff --git a/pkg/utils/retry.go b/pkg/utils/retry.go new file mode 100644 index 000000000..695dd03ae --- /dev/null +++ b/pkg/utils/retry.go @@ -0,0 +1,50 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package utils + +import ( + "time" + + "code.vikunja.io/api/pkg/log" +) + +// RetryWithBackoff executes the given function up to 3 times with exponential backoff. +// Delays between retries are 1s, 2s, 4s (total max wait: 7s). +// The name parameter is used for logging to identify what operation is being retried. +// Returns nil on success, or the last error after all retries are exhausted. +func RetryWithBackoff(name string, fn func() error) error { + const maxRetries = 3 + baseDelay := 1 * time.Second + + var err error + for attempt := 1; attempt <= maxRetries; attempt++ { + err = fn() + if err == nil { + return nil + } + + if attempt < maxRetries { + delay := baseDelay * time.Duration(1<<(attempt-1)) // exponential: 1s, 2s, 4s + log.Warningf("%s not available (attempt %d/%d), retrying in %v: %s", + name, attempt, maxRetries, delay, err) + time.Sleep(delay) + } + } + + log.Errorf("%s not available after %d attempts: %s", name, maxRetries, err) + return err +}