8e61cd7ec4
Add structured operational logging at INFO, WARN, and ERROR levels to every controller module. Standardize custom prefixes ([GEO], [SCHED], [SYNC]) to use [INFO/WARN/ERROR] [module] format. Fix misleveled logs (WARN->ERROR for data loss scenarios, WARN->INFO for routine operations). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
5.7 KiB
Go
192 lines
5.7 KiB
Go
package integrations
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// OnStackStop is called when a stack is stopped.
|
|
// Revokes active integrations where this stack is provider or target.
|
|
// Keeps enabled=true so OnStackStart can re-apply them later.
|
|
func (m *Manager) OnStackStop(_ context.Context, stackName string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
all := m.sett.GetIntegrationsForProvider(stackName)
|
|
targetAll := m.sett.GetIntegrationsForTarget(stackName)
|
|
for k, v := range targetAll {
|
|
all[k] = v
|
|
}
|
|
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStop: stack=%s integrationsFound=%d", stackName, len(all))
|
|
}
|
|
|
|
for key, state := range all {
|
|
if !state.Enabled || state.Status == "disabled" {
|
|
continue
|
|
}
|
|
|
|
provider, target, ok := ParseIntegrationKey(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
handler, hOk := m.handlers[key]
|
|
if !hOk {
|
|
continue
|
|
}
|
|
|
|
ac, err := m.buildApplyContext(provider, target)
|
|
if err != nil {
|
|
m.logger.Printf("[WARN] [integrations] Cannot build context for integration %s revoke: %v", key, err)
|
|
continue
|
|
}
|
|
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStop: revoking %s", key)
|
|
}
|
|
if err := handler.Revoke(ac); err != nil {
|
|
m.logger.Printf("[WARN] [integrations] Integration revoke on stop failed for %s: %v", key, err)
|
|
}
|
|
|
|
if provider == stackName {
|
|
state.Status = "provider_stopped"
|
|
} else {
|
|
state.Status = "target_unavailable"
|
|
}
|
|
_ = m.sett.SetIntegrationState(key, state)
|
|
m.logger.Printf("[INFO] [integrations] Integration %s suspended (stack %s stopped)", key, stackName)
|
|
}
|
|
}
|
|
|
|
// OnStackStart is called when a stack starts.
|
|
// Re-applies integrations that were previously enabled but are not currently active.
|
|
// Waits briefly for the stack manager to refresh container state.
|
|
func (m *Manager) OnStackStart(_ context.Context, stackName string) {
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStart: stack=%s, waiting 5s for state refresh", stackName)
|
|
}
|
|
// Brief delay so the stack manager's periodic status refresh
|
|
// picks up the new container state (runs every 30s).
|
|
time.Sleep(5 * time.Second)
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
all := m.sett.GetIntegrationsForProvider(stackName)
|
|
targetAll := m.sett.GetIntegrationsForTarget(stackName)
|
|
for k, v := range targetAll {
|
|
all[k] = v
|
|
}
|
|
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStart: stack=%s integrationsFound=%d", stackName, len(all))
|
|
}
|
|
|
|
for key, state := range all {
|
|
if !state.Enabled || state.Status == "active" {
|
|
continue
|
|
}
|
|
|
|
provider, target, ok := ParseIntegrationKey(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Both provider and target must be running (or starting) to re-apply.
|
|
// StateStarting = container running but healthcheck hasn't passed yet — still connectable.
|
|
provStack, pOk := m.stacks.GetStack(provider)
|
|
if !pOk || !provStack.Deployed || !isStackUp(provStack.State) {
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStart: skipping %s — provider %s not up (found=%v deployed=%v state=%v)", key, provider, pOk, pOk && provStack.Deployed, func() stacks.ContainerState { if pOk { return provStack.State }; return "" }())
|
|
}
|
|
continue
|
|
}
|
|
if target != "filebrowser" {
|
|
tgtStack, tOk := m.stacks.GetStack(target)
|
|
if !tOk || !tgtStack.Deployed || !isStackUp(tgtStack.State) {
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStart: skipping %s — target %s not up (found=%v deployed=%v state=%v)", key, target, tOk, tOk && tgtStack.Deployed, func() stacks.ContainerState { if tOk { return tgtStack.State }; return "" }())
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
handler, hOk := m.handlers[key]
|
|
if !hOk {
|
|
continue
|
|
}
|
|
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackStart: re-applying %s (currentStatus=%s)", key, state.Status)
|
|
}
|
|
|
|
ac, err := m.buildApplyContext(provider, target)
|
|
if err != nil {
|
|
m.logger.Printf("[WARN] [integrations] Cannot re-apply integration %s on start: %v", key, err)
|
|
continue
|
|
}
|
|
|
|
if err := handler.Apply(ac); err != nil {
|
|
m.logger.Printf("[ERROR] [integrations] Integration re-apply on start failed for %s: %v", key, err)
|
|
state.Status = "error"
|
|
state.LastError = err.Error()
|
|
_ = m.sett.SetIntegrationState(key, state)
|
|
continue
|
|
}
|
|
|
|
state.Status = "active"
|
|
state.LastError = ""
|
|
_ = m.sett.SetIntegrationState(key, state)
|
|
m.logger.Printf("[INFO] [integrations] Integration %s re-activated (stack %s started)", key, stackName)
|
|
}
|
|
}
|
|
|
|
// isStackUp returns true if the stack is running or starting (healthcheck pending).
|
|
func isStackUp(state stacks.ContainerState) bool {
|
|
return state == stacks.StateRunning || state == stacks.StateStarting
|
|
}
|
|
|
|
// OnStackRemove is called when a stack is removed.
|
|
// Permanently revokes and deletes integration state.
|
|
func (m *Manager) OnStackRemove(_ context.Context, stackName string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
all := m.sett.GetIntegrationsForProvider(stackName)
|
|
targetAll := m.sett.GetIntegrationsForTarget(stackName)
|
|
for k, v := range targetAll {
|
|
all[k] = v
|
|
}
|
|
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackRemove: stack=%s integrationsFound=%d", stackName, len(all))
|
|
}
|
|
|
|
for key, state := range all {
|
|
provider, target, ok := ParseIntegrationKey(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if state.Enabled {
|
|
if m.isDebug() {
|
|
m.logger.Printf("[DEBUG] [integrations] OnStackRemove: revoking enabled integration %s", key)
|
|
}
|
|
handler, hOk := m.handlers[key]
|
|
if hOk {
|
|
ac, _ := m.buildApplyContext(provider, target)
|
|
if ac != nil {
|
|
_ = handler.Revoke(ac)
|
|
}
|
|
}
|
|
}
|
|
|
|
_ = m.sett.RemoveIntegrationState(key)
|
|
m.logger.Printf("[INFO] [integrations] Integration %s removed (stack %s removed)", key, stackName)
|
|
}
|
|
}
|