feat: add resilient container restart controls
This commit is contained in:
1 parent
1bbb14470c
commit
7bb33e528c
13 files changed
+747
-28
No files matched your search
@@ -22,6 +22,16 @@ import (
|
||||
|
||||
var ErrCredentialMissing = errors.New("Update-Zugang ist noch nicht eingerichtet")
|
||||
|
||||
const (
|
||||
agentUnavailableRetryInterval = 2 * time.Second
|
||||
agentAvailableCheckInterval = time.Minute
|
||||
)
|
||||
|
||||
type updateAgent interface {
|
||||
Start(context.Context, updateprotocol.Request) error
|
||||
Status(context.Context) (updateprotocol.Status, error)
|
||||
}
|
||||
|
||||
type State struct {
|
||||
CurrentVersion string
|
||||
LatestVersion string
|
||||
@@ -46,9 +56,10 @@ type Manager struct {
|
||||
logger *slog.Logger
|
||||
apiURL string
|
||||
interval time.Duration
|
||||
agent *AgentClient
|
||||
agent updateAgent
|
||||
httpClient *http.Client
|
||||
checkMu sync.Mutex
|
||||
requestMu sync.Mutex
|
||||
stateMu sync.RWMutex
|
||||
state State
|
||||
checkSignal chan struct{}
|
||||
@@ -87,8 +98,8 @@ func New(database *store.Store, cipher *secure.Cipher, apiURL, agentSocket strin
|
||||
}
|
||||
|
||||
func (m *Manager) Start(ctx context.Context) {
|
||||
go m.monitorAgent(ctx, agentUnavailableRetryInterval, agentAvailableCheckInterval)
|
||||
go func() {
|
||||
m.refreshAgentStatus(ctx)
|
||||
initial := time.NewTimer(15 * time.Second)
|
||||
defer initial.Stop()
|
||||
var ticker *time.Ticker
|
||||
@@ -121,7 +132,28 @@ func (m *Manager) backgroundCheck(ctx context.Context) {
|
||||
if err := m.CheckNow(checkCtx); err != nil && !errors.Is(err, ErrCredentialMissing) && !errors.Is(err, context.Canceled) {
|
||||
m.logger.Warn("software update check failed", "error", err)
|
||||
}
|
||||
m.refreshAgentStatus(ctx)
|
||||
}
|
||||
|
||||
func (m *Manager) monitorAgent(ctx context.Context, unavailableInterval, availableInterval time.Duration) {
|
||||
for {
|
||||
m.refreshAgentStatus(ctx)
|
||||
delay := unavailableInterval
|
||||
if m.State().AgentAvailable {
|
||||
delay = availableInterval
|
||||
}
|
||||
timer := time.NewTimer(delay)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) TriggerCheck() {
|
||||
@@ -190,6 +222,9 @@ func (m *Manager) CheckNow(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (m *Manager) RequestUpdate(ctx context.Context, version string) error {
|
||||
m.requestMu.Lock()
|
||||
defer m.requestMu.Unlock()
|
||||
|
||||
version = strings.TrimSpace(version)
|
||||
state := m.State()
|
||||
if !validReleaseVersion(version) || version != state.LatestVersion {
|
||||
@@ -198,12 +233,16 @@ func (m *Manager) RequestUpdate(ctx context.Context, version string) error {
|
||||
if comparison, err := compareVersions(state.CurrentVersion, version); err != nil || comparison >= 0 {
|
||||
return errors.New("die ausgewählte Version ist nicht neuer als die laufende Version")
|
||||
}
|
||||
if !state.AgentAvailable {
|
||||
status, err := m.UpdateStatus(ctx)
|
||||
if err != nil {
|
||||
return errors.New("Update-Agent ist nicht erreichbar")
|
||||
}
|
||||
if !state.AgentCompatible {
|
||||
if !agentCompatible(status) {
|
||||
return errors.New("Web-App und Update-Agent müssen zuerst gemeinsam mit Docker Compose aktualisiert werden")
|
||||
}
|
||||
if status.Busy() {
|
||||
return errors.New("ein Software-Update läuft bereits")
|
||||
}
|
||||
settings, err := m.store.GetSoftwareUpdateSettings(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in new issue
Block a user