two/cmd/lab/lifecycle.go
GnomeZworc 1146d13d45
f-50: lab: lancement, état, arrêt et accès SSH des VM #50
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-10-04 14:49:34 +02:00

193 lines
4.5 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
"git.g3e.fr/syonad/two/internal/lab/machine"
"git.g3e.fr/syonad/two/internal/lab/provision"
)
const (
topologyFile = "topology.yml"
pollInterval = 5 * time.Second
stopTimeout = 30 * time.Second
)
var (
execve = syscall.Exec
procDir = "/proc"
)
func defaultDir(parts ...string) string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(append([]string{home}, parts...)...)
}
func flags(name string, stderr io.Writer) (*flag.FlagSet, *string) {
fs := flag.NewFlagSet(name, flag.ContinueOnError)
fs.SetOutput(stderr)
fs.Usage = func() { fmt.Fprint(stderr, usage) }
return fs, fs.String("run", defaultDir("lab-run"), "run directory of the lab")
}
func lab(runDir string, plan string, stdout, stderr io.Writer) (machine.Lab, bool) {
dir, err := filepath.Abs(runDir)
if err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return machine.Lab{}, false
}
p, ok := load(plan, stderr)
if !ok {
return machine.Lab{}, false
}
return machine.Lab{
Plan: p,
RunDir: dir,
ProcDir: procDir,
Runner: provision.ExecRunner{},
Poll: pollInterval,
Stop: stopTimeout,
Out: stdout,
}, true
}
func current(runDir string, stdout, stderr io.Writer) (machine.Lab, bool) {
saved := filepath.Join(runDir, topologyFile)
if _, err := os.Stat(saved); err != nil {
fmt.Fprintf(stderr, "lab: no lab in %s: %v\n", runDir, err)
return machine.Lab{}, false
}
return lab(runDir, saved, stdout, stderr)
}
func upCmd(args []string, stdout, stderr io.Writer) int {
fs, runDir := flags("up", stderr)
cacheDir := fs.String("cache", defaultDir(".cache", "two-lab"), "image cache directory")
timeout := fs.Duration("timeout", 20*time.Minute, "how long to wait for the nodes to be ready")
if err := fs.Parse(args); err != nil || fs.NArg() != 1 {
fs.Usage()
return 2
}
cache, err := filepath.Abs(*cacheDir)
if err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
if _, err := os.Stat(filepath.Join(*runDir, topologyFile)); err == nil {
previous, ok := current(*runDir, stdout, stderr)
if !ok {
return 1
}
running, err := previous.Running()
if err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
if len(running) > 0 {
fmt.Fprintf(stderr, "lab: lab %s is still running in %s: 'lab down' first\n", previous.Plan.Name, previous.RunDir)
return 1
}
}
l, ok := lab(*runDir, fs.Arg(0), stdout, stderr)
if !ok {
return 1
}
source, err := os.ReadFile(fs.Arg(0))
if err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
if err := os.MkdirAll(l.RunDir, 0o700); err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
if err := os.WriteFile(filepath.Join(l.RunDir, topologyFile), source, 0o600); err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
fetcher := provision.Fetcher{Client: &http.Client{}, CacheDir: cache}
if err := l.Up(ctx, fetcher, *timeout); err != nil {
fmt.Fprintf(stderr, "lab: %v\nlab: started nodes keep running: 'lab status', 'lab down'\n", err)
return 1
}
return 0
}
func statusCmd(args []string, stdout, stderr io.Writer) int {
fs, runDir := flags("status", stderr)
if err := fs.Parse(args); err != nil || fs.NArg() != 0 {
fs.Usage()
return 2
}
l, ok := current(*runDir, stdout, stderr)
if !ok {
return 1
}
if err := l.Status(stdout); err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
return 0
}
func downCmd(args []string, stdout, stderr io.Writer) int {
fs, runDir := flags("down", stderr)
if err := fs.Parse(args); err != nil || fs.NArg() != 0 {
fs.Usage()
return 2
}
l, ok := current(*runDir, stdout, stderr)
if !ok {
return 1
}
if err := l.Down(context.Background()); err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
return 0
}
func sshCmd(args []string, stdout, stderr io.Writer) int {
fs, runDir := flags("ssh", stderr)
if err := fs.Parse(args); err != nil || fs.NArg() < 1 {
fs.Usage()
return 2
}
l, ok := current(*runDir, stdout, stderr)
if !ok {
return 1
}
argv, err := l.SSH(fs.Arg(0), isTerminal(os.Stdin), fs.Args()[1:])
if err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
path, err := exec.LookPath(argv[0])
if err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
if err := execve(path, argv, os.Environ()); err != nil {
fmt.Fprintf(stderr, "lab: %v\n", err)
return 1
}
return 0
}