wisski-cloud-distillery/internal/component/snapshots/snapshot.go
2022-11-16 13:07:08 +01:00

124 lines
3.4 KiB
Go

package snapshots
import (
"fmt"
"io"
"path/filepath"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/lib/collection"
"github.com/tkw1536/goprogram/status"
"github.com/tkw1536/goprogram/stream"
"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 *Manager) NewSnapshot(instance instances.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) {
// 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(io, snapshots, instance, false)
snapshot.ErrBlackbox = snapshot.makeParts(io, snapshots, instance, true)
snapshot.EndTime = time.Now().UTC()
return nil
}, io, "Writing snapshot files")
slices.Sort(snapshot.Manifest)
return
}
func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Manager, instance instances.WissKI, needsRunning bool) map[string]error {
if !needsRunning && !snapshot.Description.Keepalive {
stack := instance.Barrel()
logging.LogMessage(ios, "Stopping instance")
snapshot.ErrStop = stack.Down(ios)
defer func() {
logging.LogMessage(ios, "Starting instance")
snapshot.ErrStart = stack.Up(ios)
}()
}
// handle writing the manifest!
manifest, done := snapshot.handleManifest(snapshot.Description.Dest)
defer done()
// create a new status
st := status.NewWithCompat(ios.Stdout, 0)
st.Start()
defer st.Stop()
// get all the components
comps := collection.FilterClone(snapshots.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(
snapshots.Environment,
stream.NewIOStream(writer, writer, nil, 0),
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
}