feat: establish secure update foundation
Container-Image veröffentlichen / container (push) v1.4.0 wurde manuell veröffentlicht und verifiziert
Container-Image veröffentlichen / container (push) v1.4.0 wurde manuell veröffentlicht und verifiziert
This commit is contained in:
1 parent
f02ec9977c
commit
bb60c06c0e
28 files changed
+1403
-280
No files matched your search
@@ -32,7 +32,11 @@ type State struct {
|
||||
CredentialConfigured bool
|
||||
UpdateAvailable bool
|
||||
AgentAvailable bool
|
||||
AgentCompatible bool
|
||||
AgentStatus updateprotocol.Status
|
||||
AgentVersion string
|
||||
AgentProtocol int
|
||||
ComposeSyncRequired bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
@@ -76,6 +80,7 @@ func New(database *store.Store, cipher *secure.Cipher, apiURL, agentSocket strin
|
||||
},
|
||||
},
|
||||
}
|
||||
settings = manager.reconcileInstalledVersion(context.Background(), settings)
|
||||
manager.setStoredState(settings)
|
||||
return manager, nil
|
||||
}
|
||||
@@ -192,6 +197,12 @@ 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 {
|
||||
return errors.New("Update-Agent ist nicht erreichbar")
|
||||
}
|
||||
if !state.AgentCompatible {
|
||||
return errors.New("Web-App und Update-Agent müssen zuerst gemeinsam mit Docker Compose aktualisiert werden")
|
||||
}
|
||||
settings, err := m.store.GetSoftwareUpdateSettings(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -203,8 +214,11 @@ func (m *Manager) RequestUpdate(ctx context.Context, version string) error {
|
||||
if err := m.store.SetDesiredSoftwareVersion(ctx, version); err != nil {
|
||||
return err
|
||||
}
|
||||
request := updateprotocol.Request{Version: version, RegistryUsername: settings.RegistryUsername, RegistryToken: token}
|
||||
request := updateprotocol.Request{Version: version, CurrentVersion: state.CurrentVersion, RegistryUsername: settings.RegistryUsername, RegistryToken: token}
|
||||
if err := m.agent.Start(ctx, request); err != nil {
|
||||
cleanupCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
m.clearDesiredVersion(cleanupCtx, version)
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
settings, err = m.store.GetSoftwareUpdateSettings(ctx)
|
||||
@@ -217,11 +231,26 @@ func (m *Manager) RequestUpdate(ctx context.Context, version string) error {
|
||||
func (m *Manager) UpdateStatus(ctx context.Context) (updateprotocol.Status, error) {
|
||||
status, err := m.agent.Status(ctx)
|
||||
m.stateMu.Lock()
|
||||
desiredVersion := m.state.DesiredVersion
|
||||
m.state.AgentAvailable = err == nil
|
||||
if err == nil {
|
||||
m.state.AgentStatus = status
|
||||
m.state.AgentVersion = status.AgentVersion
|
||||
m.state.AgentProtocol = status.Protocol
|
||||
m.state.AgentCompatible = agentCompatible(status)
|
||||
m.state.ComposeSyncRequired = !m.state.AgentCompatible
|
||||
} else {
|
||||
m.state.AgentCompatible = false
|
||||
m.state.ComposeSyncRequired = false
|
||||
}
|
||||
m.stateMu.Unlock()
|
||||
if err == nil && status.Phase == updateprotocol.PhaseFailed && status.TargetVersion != "" {
|
||||
m.clearDesiredVersion(ctx, status.TargetVersion)
|
||||
} else if err == nil && status.Phase == updateprotocol.PhaseIdle && desiredVersion != "" {
|
||||
// An agent restart intentionally resets its in-memory queue. Do not leave
|
||||
// an update marked as pending when no update can still be running.
|
||||
m.clearDesiredVersion(ctx, desiredVersion)
|
||||
}
|
||||
return status, err
|
||||
}
|
||||
|
||||
@@ -290,11 +319,55 @@ func (m *Manager) setStoredState(settings model.SoftwareUpdateSettings) {
|
||||
}
|
||||
m.stateMu.Lock()
|
||||
state.AgentAvailable = m.state.AgentAvailable
|
||||
state.AgentCompatible = m.state.AgentCompatible
|
||||
state.AgentStatus = m.state.AgentStatus
|
||||
state.AgentVersion = m.state.AgentVersion
|
||||
state.AgentProtocol = m.state.AgentProtocol
|
||||
state.ComposeSyncRequired = state.AgentAvailable && !state.AgentCompatible
|
||||
m.state = state
|
||||
m.stateMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) reconcileInstalledVersion(ctx context.Context, settings model.SoftwareUpdateSettings) model.SoftwareUpdateSettings {
|
||||
if settings.DesiredVersion == "" {
|
||||
return settings
|
||||
}
|
||||
comparison, err := compareVersions(buildinfo.Version, settings.DesiredVersion)
|
||||
if err != nil || comparison < 0 {
|
||||
return settings
|
||||
}
|
||||
if err := m.store.ClearDesiredSoftwareVersion(ctx, settings.DesiredVersion); err != nil {
|
||||
m.logger.Warn("clear completed software update state", "error", err)
|
||||
return settings
|
||||
}
|
||||
settings.DesiredVersion = ""
|
||||
return settings
|
||||
}
|
||||
|
||||
func (m *Manager) clearDesiredVersion(ctx context.Context, expected string) {
|
||||
if expected == "" {
|
||||
return
|
||||
}
|
||||
if err := m.store.ClearDesiredSoftwareVersion(ctx, expected); err != nil {
|
||||
m.logger.Warn("clear stale software update state", "error", err)
|
||||
return
|
||||
}
|
||||
settings, err := m.store.GetSoftwareUpdateSettings(ctx)
|
||||
if err != nil {
|
||||
m.logger.Warn("refresh software update state", "error", err)
|
||||
return
|
||||
}
|
||||
m.setStoredState(settings)
|
||||
}
|
||||
|
||||
func agentCompatible(status updateprotocol.Status) bool {
|
||||
if status.Protocol < buildinfo.MinimumAgentProtocol || status.Protocol > buildinfo.UpdateProtocolVersion {
|
||||
return false
|
||||
}
|
||||
comparison, err := compareVersions(status.AgentVersion, buildinfo.MinimumAgentVersion)
|
||||
return err == nil && comparison >= 0
|
||||
}
|
||||
|
||||
func (m *Manager) refreshAgentStatus(ctx context.Context) {
|
||||
statusCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
Reference in new issue
Block a user