docker_health.go

64 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
package platform

import (
	"fmt"
	"log"
	"strings"
	"time"
)

// WaitForHealthy waits for a Docker container to report healthy status.
func (s *Server) WaitForHealthy(container string, timeoutSecs int) error {
	log.Printf("   Waiting for %s to become healthy...", container)

	for i := 0; i < timeoutSecs; i += 5 {
		status, err := s.SSH("docker", "inspect", "--format={{.State.Health.Status}}", container)
		if err == nil {
			status = strings.TrimSpace(status)
			switch status {
			case "healthy":
				log.Printf("   %s is healthy", container)
				return nil
			case "unhealthy":
				logs, _ := s.SSH("docker", "logs", "--tail=20", container)
				return fmt.Errorf("container unhealthy, logs:\n%s", logs)
			}
		}
		time.Sleep(5 * time.Second)
	}

	return fmt.Errorf("health check timeout after %ds", timeoutSecs)
}

// Backup stops and renames the current container for rollback.
func (s *Server) Backup(name string) {
	backupName := name + "-backup"
	s.SSH("docker", "rm", "-f", backupName)
	s.SSH("docker", "stop", name)
	s.SSH("docker", "rename", name, backupName)
}

// Rollback restores the previous container version from backup.
func (s *Server) Rollback(name string) error {
	backupName := name + "-backup"

	if _, err := s.SSH("docker", "inspect", backupName); err != nil {
		log.Printf("   No backup found for %s, cannot rollback", name)
		return nil
	}

	log.Printf("   Rolling back %s...", name)

	s.SSH("docker", "rm", "-f", name)

	if _, err := s.SSH("docker", "rename", backupName, name); err != nil {
		return fmt.Errorf("rename backup: %w", err)
	}

	if _, err := s.SSH("docker", "start", name); err != nil {
		return fmt.Errorf("start backup: %w", err)
	}

	log.Printf("   Rolled back to previous version of %s", name)
	return nil
}