package exporter import ( "context" "fmt" "io" "path/filepath" "time" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/status" "golang.org/x/exp/slices" ) // SnapshotDescription is a description for a snapshot type SnapshotDescription struct { Dest string // destination path Keepalive bool // should we keep the instance alive while making the snapshot? } // Snapshot represents the result of generating a snapshot type Snapshot struct { Description SnapshotDescription Instance models.Instance // Start and End Time of the snapshot StartTime time.Time EndTime time.Time // Generic Panic that may have occured ErrPanic interface{} ErrStart error ErrStop error ErrWhitebox map[string]error ErrBlackbox map[string]error // List of files included WithManifest } // Snapshot creates a new snapshot of this instance into dest func (snapshots *Exporter) NewSnapshot(ctx context.Context, instance *wisski.WissKI, progress io.Writer, desc SnapshotDescription) (snapshot Snapshot) { logging.LogMessage(progress, ctx, "Locking instance") if !instance.Locker().TryLock(ctx) { err := locker.Locked logging.ProgressF(progress, ctx, "%v", err) logging.LogMessage(progress, ctx, "Aborting snapshot creation") return Snapshot{ ErrPanic: err, } } defer func() { logging.LogMessage(progress, ctx, "Unlocking instance") instance.Locker().Unlock(ctx) }() // setup the snapshot snapshot.Description = desc snapshot.Instance = instance.Instance // capture anything critical, and write the end time defer func() { snapshot.ErrPanic = recover() }() // do the create keeping track of time! logging.LogOperation(func() error { snapshot.StartTime = time.Now().UTC() snapshot.ErrWhitebox = snapshot.makeParts(ctx, progress, snapshots, instance, false) snapshot.ErrBlackbox = snapshot.makeParts(ctx, progress, snapshots, instance, true) snapshot.EndTime = time.Now().UTC() return nil }, progress, ctx, "Writing snapshot files") slices.Sort(snapshot.Manifest) return } func (snapshot *Snapshot) makeParts(ctx context.Context, progress io.Writer, snapshots *Exporter, instance *wisski.WissKI, needsRunning bool) map[string]error { if !needsRunning && !snapshot.Description.Keepalive { stack := instance.Barrel().Stack() logging.LogMessage(progress, ctx, "Stopping instance") snapshot.ErrStop = stack.Down(ctx, progress) defer func() { logging.LogMessage(progress, ctx, "Starting instance") snapshot.ErrStart = stack.Up(ctx, progress) }() } // handle writing the manifest! manifest, done := snapshot.handleManifest(snapshot.Description.Dest) defer done() // create a new status st := status.NewWithCompat(progress, 0) st.Start() defer st.Stop() // get all the components comps := collection.FilterClone(snapshots.Dependencies.Snapshotable, func(sc component.Snapshotable) bool { return sc.SnapshotNeedsRunning() == needsRunning }) results := make(map[string]error, len(comps)) errors := status.Group[component.Snapshotable, error]{ PrefixString: func(item component.Snapshotable, index int) string { return fmt.Sprintf("[snapshot %q]: ", item.Name()) }, PrefixAlign: true, Handler: func(sc component.Snapshotable, index int, writer io.Writer) error { return sc.Snapshot( instance.Instance, component.NewStagingContext( ctx, snapshots.Environment, writer, filepath.Join(snapshot.Description.Dest, sc.SnapshotName()), manifest, ), ) }, ResultString: status.DefaultErrorString[component.Snapshotable], }.Use(st, comps) for i, wc := range comps { results[wc.Name()] = errors[i] } return results }