// Package infra gère l'infrastructure (serveurs, containers, nginx). package infra import ( "context" "encoding/json" "fmt" "strings" ) // IncusContainer représente un container Incus retourné par incus list. type IncusContainer struct { Name string `json:"name"` State string `json:"state"` IPv4 []string `json:"ipv4"` IPv6 []string `json:"ipv6"` Type string `json:"type"` Snapshots int `json:"snapshots"` Location string `json:"location"` Image string `json:"-"` // Rempli séparément CreatedAt string `json:"created_at"` } // incusListJSON représente le format JSON de incus list. type incusListJSON struct { Name string `json:"name"` Status string `json:"status"` Type string `json:"type"` Snapshots int `json:"snapshots"` Location string `json:"location"` IPv4 string `json:"ipv4"` IPv6 string `json:"ipv6"` } // ListIncusContainers liste les containers Incus sur le serveur. func (c *SSHClient) ListIncusContainers(ctx context.Context) ([]IncusContainer, error) { // Récupérer la liste en format JSON result, err := c.Exec(ctx, "incus list --format json") if err != nil { return nil, fmt.Errorf("incus list: %w", err) } if result.ExitCode != 0 { return nil, fmt.Errorf("incus list failed: %s", result.Stderr) } // Parser le JSON var rawContainers []struct { Name string `json:"name"` Status string `json:"status"` StatusCode int `json:"status_code"` Type string `json:"type"` Config struct { Image string `json:"image.description"` } `json:"config"` State struct { Network map[string]struct { Addresses []struct { Address string `json:"address"` Family string `json:"family"` Scope string `json:"scope"` } `json:"addresses"` } `json:"network"` } `json:"state"` Snapshots []interface{} `json:"snapshots"` Location string `json:"location"` CreatedAt string `json:"created_at"` } if err := json.Unmarshal([]byte(result.Stdout), &rawContainers); err != nil { return nil, fmt.Errorf("parse incus json: %w", err) } // Convertir en notre format containers := make([]IncusContainer, 0, len(rawContainers)) for _, rc := range rawContainers { container := IncusContainer{ Name: rc.Name, State: strings.ToLower(rc.Status), Type: rc.Type, Snapshots: len(rc.Snapshots), Location: rc.Location, Image: rc.Config.Image, CreatedAt: rc.CreatedAt, } // Extraire les IPs for _, net := range rc.State.Network { for _, addr := range net.Addresses { if addr.Scope == "global" { if addr.Family == "inet" { container.IPv4 = append(container.IPv4, addr.Address) } else if addr.Family == "inet6" { container.IPv6 = append(container.IPv6, addr.Address) } } } } containers = append(containers, container) } return containers, nil } // GetIncusContainer récupère les infos d'un container spécifique. func (c *SSHClient) GetIncusContainer(ctx context.Context, name string) (*IncusContainer, error) { containers, err := c.ListIncusContainers(ctx) if err != nil { return nil, err } for _, container := range containers { if container.Name == name { return &container, nil } } return nil, fmt.Errorf("container %s not found", name) } // StartIncusContainer démarre un container. func (c *SSHClient) StartIncusContainer(ctx context.Context, name string) error { result, err := c.Exec(ctx, fmt.Sprintf("incus start %s", name)) if err != nil { return fmt.Errorf("incus start: %w", err) } if result.ExitCode != 0 { return fmt.Errorf("incus start %s failed: %s", name, result.Stderr) } return nil } // StopIncusContainer arrête un container. func (c *SSHClient) StopIncusContainer(ctx context.Context, name string) error { result, err := c.Exec(ctx, fmt.Sprintf("incus stop %s", name)) if err != nil { return fmt.Errorf("incus stop: %w", err) } if result.ExitCode != 0 { return fmt.Errorf("incus stop %s failed: %s", name, result.Stderr) } return nil } // RestartIncusContainer redémarre un container. func (c *SSHClient) RestartIncusContainer(ctx context.Context, name string) error { result, err := c.Exec(ctx, fmt.Sprintf("incus restart %s", name)) if err != nil { return fmt.Errorf("incus restart: %w", err) } if result.ExitCode != 0 { return fmt.Errorf("incus restart %s failed: %s", name, result.Stderr) } return nil } // ExecInContainer exécute une commande dans un container. func (c *SSHClient) ExecInContainer(ctx context.Context, containerName, cmd string) (*SSHResult, error) { fullCmd := fmt.Sprintf("incus exec %s -- %s", containerName, cmd) return c.Exec(ctx, fullCmd) } // ExecInContainerSimple exécute une commande et retourne stdout. func (c *SSHClient) ExecInContainerSimple(ctx context.Context, containerName, cmd string) (string, error) { result, err := c.ExecInContainer(ctx, containerName, cmd) if err != nil { return "", err } if result.ExitCode != 0 { return "", fmt.Errorf("command failed in %s (exit %d): %s", containerName, result.ExitCode, result.Stderr) } return strings.TrimSpace(result.Stdout), nil } // PushFileToContainer envoie un fichier vers un container. func (c *SSHClient) PushFileToContainer(ctx context.Context, containerName, localPath, remotePath string) error { // D'abord copier vers le serveur hôte tempPath := fmt.Sprintf("/tmp/incus-push-%d", ctx.Value("request_id")) if err := c.CopyFile(ctx, localPath, tempPath); err != nil { return fmt.Errorf("copy to host: %w", err) } // Puis push vers le container result, err := c.Exec(ctx, fmt.Sprintf("incus file push %s %s%s && rm %s", tempPath, containerName, remotePath, tempPath)) if err != nil { return fmt.Errorf("incus file push: %w", err) } if result.ExitCode != 0 { return fmt.Errorf("incus file push failed: %s", result.Stderr) } return nil } // PullFileFromContainer récupère un fichier depuis un container. func (c *SSHClient) PullFileFromContainer(ctx context.Context, containerName, remotePath, localPath string) error { tempPath := fmt.Sprintf("/tmp/incus-pull-%d", ctx.Value("request_id")) // Pull depuis le container vers l'hôte result, err := c.Exec(ctx, fmt.Sprintf("incus file pull %s%s %s", containerName, remotePath, tempPath)) if err != nil { return fmt.Errorf("incus file pull: %w", err) } if result.ExitCode != 0 { return fmt.Errorf("incus file pull failed: %s", result.Stderr) } // Puis copier vers local if err := c.CopyFrom(ctx, tempPath, localPath); err != nil { return fmt.Errorf("copy from host: %w", err) } // Nettoyer c.Exec(ctx, fmt.Sprintf("rm %s", tempPath)) return nil } // GetContainerLogs récupère les logs d'un container. func (c *SSHClient) GetContainerLogs(ctx context.Context, containerName string, lines int) (string, error) { if lines <= 0 { lines = 100 } return c.ExecInContainerSimple(ctx, containerName, fmt.Sprintf("journalctl -n %d --no-pager", lines)) }