diff --git a/cmd/backup.go b/cmd/backup.go index 4b0c5fb..66b502a 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -41,12 +41,12 @@ func (bk backup) Run(context wisski_distillery.Context) error { // prune old backups if !bk.NoPrune { defer logging.LogOperation(func() error { - return dis.Exporter().PruneExports(context.IOStream) + return dis.Exporter().PruneExports(context.Context, context.IOStream) }, context.IOStream, "Pruning old backups") } // do the handling - err := dis.Exporter().MakeExport(context.IOStream, exporter.ExportTask{ + err := dis.Exporter().MakeExport(context.Context, context.IOStream, exporter.ExportTask{ Dest: bk.Positionals.Dest, StagingOnly: bk.StagingOnly, diff --git a/cmd/blind_update.go b/cmd/blind_update.go index 8018505..29cb5c7 100644 --- a/cmd/blind_update.go +++ b/cmd/blind_update.go @@ -40,7 +40,7 @@ var errBlindUpdateFailed = exit.Error{ func (bu blindUpdate) Run(context wisski_distillery.Context) error { // find all the instances! - wissKIs, err := context.Environment.Instances().Load(bu.Positionals.Slug...) + wissKIs, err := context.Environment.Instances().Load(context.Context, bu.Positionals.Slug...) if err != nil { return err } @@ -52,7 +52,7 @@ func (bu blindUpdate) Run(context wisski_distillery.Context) error { // and do the actual blind_update! return status.StreamGroup(context.IOStream, bu.Parallel, func(instance *wisski.WissKI, str stream.IOStream) error { - return instance.Drush().Update(str) + return instance.Drush().Update(context.Context, str) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("blind_update %q", item.Slug) })) diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index e4dc42d..ff0d20a 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -115,7 +115,7 @@ func (bs cBootstrap) Run(context wisski_distillery.Context) error { return errBoostrapFailedToCopyExe.WithMessageF(err) } - err = fsx.CopyFile(env, wdcliPath, exe) + err = fsx.CopyFile(context.Context, env, wdcliPath, exe) if err != nil && err != fsx.ErrCopySameFile { return errBoostrapFailedToCopyExe.WithMessageF(err) } diff --git a/cmd/cron.go b/cmd/cron.go index 28e2cbd..7edebb5 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -33,14 +33,14 @@ func (cron) Description() wisski_distillery.Description { func (cr cron) Run(context wisski_distillery.Context) error { // find all the instances! - wissKIs, err := context.Environment.Instances().Load(cr.Positionals.Slug...) + wissKIs, err := context.Environment.Instances().Load(context.Context, cr.Positionals.Slug...) if err != nil { return err } // and do the actual blind_update! return status.StreamGroup(context.IOStream, cr.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { - return instance.Drush().Cron(io) + return instance.Drush().Cron(context.Context, io) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("cron %q", item.Slug) })) diff --git a/cmd/drupal_setting.go b/cmd/drupal_setting.go index 1037f80..b8cef74 100644 --- a/cmd/drupal_setting.go +++ b/cmd/drupal_setting.go @@ -40,14 +40,14 @@ var errSettingSet = exit.Error{ } func (ds setting) Run(context wisski_distillery.Context) error { - instance, err := context.Environment.Instances().WissKI(ds.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, ds.Positionals.Slug) if err != nil { return err } if ds.Positionals.Value == "" { // get the setting - value, err := instance.Settings().Get(nil, ds.Positionals.Setting) + value, err := instance.Settings().Get(context.Context, nil, ds.Positionals.Setting) if err != nil { return errSettingGet.Wrap(err) } @@ -69,7 +69,7 @@ func (ds setting) Run(context wisski_distillery.Context) error { } // set the serialized value! - if err := instance.Settings().Set(nil, ds.Positionals.Setting, data); err != nil { + if err := instance.Settings().Set(context.Context, nil, ds.Positionals.Setting, data); err != nil { return errSettingSet.Wrap(err) } diff --git a/cmd/drupal_user.go b/cmd/drupal_user.go index 8a0ce3c..8d63388 100644 --- a/cmd/drupal_user.go +++ b/cmd/drupal_user.go @@ -75,7 +75,7 @@ var errPasswordsNotIdentical = exit.Error{ } func (du duser) Run(context wisski_distillery.Context) error { - instance, err := context.Environment.Instances().WissKI(du.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, du.Positionals.Slug) if err != nil { return err } @@ -94,7 +94,7 @@ func (du duser) Run(context wisski_distillery.Context) error { } func (du duser) login(context wisski_distillery.Context, instance *wisski.WissKI) error { - link, err := instance.Users().Login(nil, du.Positionals.User) + link, err := instance.Users().Login(context.Context, nil, du.Positionals.User) if err != nil { return err } @@ -110,7 +110,7 @@ var errPasswordFound = exit.Error{ func (du duser) checkCommonPassword(context wisski_distillery.Context, instance *wisski.WissKI) error { users := instance.Users() - entities, err := users.All(nil) + entities, err := users.All(context.Context, nil) if err != nil { return err } @@ -121,19 +121,19 @@ func (du duser) checkCommonPassword(context wisski_distillery.Context, instance }, PrefixAlign: true, Handler: func(user wstatus.User, index int, writer io.Writer) error { - pv, err := users.GetPasswordValidator(string(user.Name)) + pv, err := users.GetPasswordValidator(context.Context, string(user.Name)) if err != nil { return err } defer pv.Close() - return pv.CheckDictionary(context.Environment.Context(), writer) + return pv.CheckDictionary(context.Context, writer) }, }, entities) } func (du duser) checkPasswordInteractive(context wisski_distillery.Context, instance *wisski.WissKI) error { - validator, err := instance.Users().GetPasswordValidator(du.Positionals.User) + validator, err := instance.Users().GetPasswordValidator(context.Context, du.Positionals.User) if err != nil { return err } @@ -151,7 +151,7 @@ func (du duser) checkPasswordInteractive(context wisski_distillery.Context, inst break } - if validator.Check(candidate) { + if validator.Check(context.Context, candidate) { context.Println("check passed") } else { context.Println("check did not pass") @@ -180,5 +180,5 @@ func (du duser) resetPassword(context wisski_distillery.Context, instance *wissk return errPasswordsNotIdentical } - return instance.Users().SetPassword(nil, du.Positionals.User, passwd1) + return instance.Users().SetPassword(context.Context, nil, du.Positionals.User, passwd1) } diff --git a/cmd/info.go b/cmd/info.go index e8967b0..a7706f2 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -29,12 +29,12 @@ func (info) Description() wisski_distillery.Description { } func (i info) Run(context wisski_distillery.Context) error { - instance, err := context.Environment.Instances().WissKI(i.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, i.Positionals.Slug) if err != nil { return err } - info, err := instance.Info().Information(false) + info, err := instance.Info().Information(context.Context, false) if err != nil { return err } diff --git a/cmd/instance_lock.go b/cmd/instance_lock.go index 76ec934..d65c806 100644 --- a/cmd/instance_lock.go +++ b/cmd/instance_lock.go @@ -46,20 +46,20 @@ var errNotUnlock = exit.Error{ } func (l instanceLock) Run(context wisski_distillery.Context) error { - instance, err := context.Environment.Instances().WissKI(l.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, l.Positionals.Slug) if err != nil { return err } if l.Unlock { - if !instance.Locker().TryUnlock() { + if !instance.Locker().TryUnlock(context.Context) { return errNotUnlock } context.Println("unlocked") return nil } - if !instance.Locker().TryLock() { + if !instance.Locker().TryLock(context.Context) { return locker.Locked } diff --git a/cmd/ls.go b/cmd/ls.go index 4722938..665cc45 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -25,7 +25,7 @@ func (ls) Description() wisski_distillery.Description { } func (l ls) Run(context wisski_distillery.Context) error { - instances, err := context.Environment.Instances().Load(l.Positionals.Slug...) + instances, err := context.Environment.Instances().Load(context.Context, l.Positionals.Slug...) if err != nil { return err } diff --git a/cmd/make_mysql_account.go b/cmd/make_mysql_account.go index 31daf2e..f099606 100644 --- a/cmd/make_mysql_account.go +++ b/cmd/make_mysql_account.go @@ -50,7 +50,7 @@ func (mma makeMysqlAccount) Run(context wisski_distillery.Context) error { return errUnableToReadPassword.WithMessageF(err) } - if err := dis.SQL().CreateSuperuser(username, password, false); err != nil { + if err := dis.SQL().CreateSuperuser(context.Context, username, password, false); err != nil { return err } diff --git a/cmd/mysql.go b/cmd/mysql.go index 091997c..f8bc94e 100644 --- a/cmd/mysql.go +++ b/cmd/mysql.go @@ -32,7 +32,7 @@ func (mysql) Description() wisski_distillery.Description { } func (ms mysql) Run(context wisski_distillery.Context) error { - code, err := context.Environment.SQL().Shell(context.IOStream, ms.Positionals.Args...) + code, err := context.Environment.SQL().Shell(context.Context, context.IOStream, ms.Positionals.Args...) if err != nil { return err } diff --git a/cmd/pathbuilders.go b/cmd/pathbuilders.go index 8a80b00..8b8f030 100644 --- a/cmd/pathbuilders.go +++ b/cmd/pathbuilders.go @@ -39,14 +39,14 @@ var errNoPathbuilder = exit.Error{ func (pb pathbuilders) Run(context wisski_distillery.Context) error { // get the wisski - instance, err := context.Environment.Instances().WissKI(pb.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, pb.Positionals.Slug) if err != nil { return err } // get all of the pathbuilders if pb.Positionals.Name == "" { - names, err := instance.Pathbuilder().All(nil) + names, err := instance.Pathbuilder().All(context.Context, nil) if err != nil { return errPathbuilders.WithMessageF(err) } @@ -57,7 +57,7 @@ func (pb pathbuilders) Run(context wisski_distillery.Context) error { } // get all the pathbuilders - xml, err := instance.Pathbuilder().Get(nil, pb.Positionals.Name) + xml, err := instance.Pathbuilder().Get(context.Context, nil, pb.Positionals.Name) if xml == "" { return errNoPathbuilder.WithMessageF(pb.Positionals.Name) } diff --git a/cmd/prefixes.go b/cmd/prefixes.go index dd7698c..b28421d 100644 --- a/cmd/prefixes.go +++ b/cmd/prefixes.go @@ -31,12 +31,12 @@ var errPrefixesGeneric = exit.Error{ } func (p prefixes) Run(context wisski_distillery.Context) error { - instance, err := context.Environment.Instances().WissKI(p.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, p.Positionals.Slug) if err != nil { return err } - prefixes, err := instance.Prefixes().All(nil) + prefixes, err := instance.Prefixes().All(context.Context, nil) if err != nil { return errPrefixesGeneric.Wrap(err) } diff --git a/cmd/provision.go b/cmd/provision.go index bc9a6c3..6a2d754 100644 --- a/cmd/provision.go +++ b/cmd/provision.go @@ -45,7 +45,7 @@ func (p provision) Run(context wisski_distillery.Context) error { // check that it doesn't already exist logging.LogMessage(context.IOStream, "Provisioning new WissKI instance %s", slug) - if exists, err := dis.Instances().Has(slug); err != nil || exists { + if exists, err := dis.Instances().Has(context.Context, slug); err != nil || exists { return errProvisionAlreadyExists.WithMessageF(slug) } @@ -63,7 +63,7 @@ func (p provision) Run(context wisski_distillery.Context) error { // Store in the instances table! if err := logging.LogOperation(func() error { - if err := instance.Bookkeeping().Save(); err != nil { + if err := instance.Bookkeeping().Save(context.Context); err != nil { return errProvisionGeneric.WithMessageF(slug, err) } @@ -77,7 +77,7 @@ func (p provision) Run(context wisski_distillery.Context) error { domain := instance.Domain() for _, pc := range dis.Provisionable() { logging.LogMessage(context.IOStream, "Provisioning %s resources", pc.Name()) - err := pc.Provision(instance.Instance, domain) + err := pc.Provision(context.Context, instance.Instance, domain) if err != nil { return err } @@ -90,7 +90,7 @@ func (p provision) Run(context wisski_distillery.Context) error { // run the provision script if err := logging.LogOperation(func() error { - if err := instance.Provisioner().Provision(context.IOStream); err != nil { + if err := instance.Provisioner().Provision(context.Context, context.IOStream); err != nil { return errProvisionGeneric.WithMessageF(slug, err) } @@ -101,7 +101,7 @@ func (p provision) Run(context wisski_distillery.Context) error { // start the container! logging.LogMessage(context.IOStream, "Starting Container") - if err := instance.Barrel().Stack().Up(context.IOStream); err != nil { + if err := instance.Barrel().Stack().Up(context.Context, context.IOStream); err != nil { return err } diff --git a/cmd/purge.go b/cmd/purge.go index cd34096..046c645 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -59,7 +59,7 @@ func (p purge) Run(context wisski_distillery.Context) error { // load the instance (first via bookkeeping, then via defaults) logging.LogMessage(context.IOStream, "Checking bookkeeping table") - instance, err := dis.Instances().WissKI(slug) + instance, err := dis.Instances().WissKI(context.Context, slug) if err == instances.ErrWissKINotFound { context.Println("Not found in bookkeeping table, assuming defaults") instance, err = dis.Instances().Create(slug) @@ -70,7 +70,7 @@ func (p purge) Run(context wisski_distillery.Context) error { // remove docker stack logging.LogMessage(context.IOStream, "Stopping and removing docker container") - if err := instance.Barrel().Stack().Down(context.IOStream); err != nil { + if err := instance.Barrel().Stack().Down(context.Context, context.IOStream); err != nil { context.EPrintln(err) } @@ -85,7 +85,7 @@ func (p purge) Run(context wisski_distillery.Context) error { domain := instance.Domain() for _, pc := range dis.Provisionable() { logging.LogMessage(context.IOStream, "Purging %s resources", pc.Name()) - err := pc.Purge(instance.Instance, domain) + err := pc.Purge(context.Context, instance.Instance, domain) if err != nil { return err } @@ -98,13 +98,13 @@ func (p purge) Run(context wisski_distillery.Context) error { // remove from bookkeeping logging.LogMessage(context.IOStream, "Removing instance from bookkeeping") - if err := instance.Bookkeeping().Delete(); err != nil { + if err := instance.Bookkeeping().Delete(context.Context); err != nil { context.EPrintln(err) } // remove the filesystem logging.LogMessage(context.IOStream, "Remove lock data") - if instance.Locker().TryUnlock() { + if instance.Locker().TryUnlock(context.Context) { context.EPrintln("instance was not locked") } diff --git a/cmd/rebuild.go b/cmd/rebuild.go index 8f40f06..6bcca35 100644 --- a/cmd/rebuild.go +++ b/cmd/rebuild.go @@ -40,14 +40,14 @@ func (rb rebuild) Run(context wisski_distillery.Context) error { dis := context.Environment // find the instances - wissKIs, err := dis.Instances().Load(rb.Positionals.Slug...) + wissKIs, err := dis.Instances().Load(context.Context, rb.Positionals.Slug...) if err != nil { return err } // and do the actual rebuild return status.StreamGroup(context.IOStream, rb.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { - return instance.Barrel().Build(io, true) + return instance.Barrel().Build(context.Context, io, true) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("rebuild %q", item.Slug) })) diff --git a/cmd/reserve.go b/cmd/reserve.go index eb0aa16..9bdb81f 100644 --- a/cmd/reserve.go +++ b/cmd/reserve.go @@ -46,7 +46,7 @@ func (r reserve) Run(context wisski_distillery.Context) error { // check that it doesn't already exist logging.LogMessage(context.IOStream, "Reserving new WissKI instance %s", slug) - if exists, err := dis.Instances().Has(slug); err != nil || exists { + if exists, err := dis.Instances().Has(context.Context, slug); err != nil || exists { return errProvisionAlreadyExists.WithMessageF(slug) } @@ -66,13 +66,13 @@ func (r reserve) Run(context wisski_distillery.Context) error { s := instance.Reserve().Stack() { if err := logging.LogOperation(func() error { - return s.Install(context.IOStream, component.InstallationContext{}) + return s.Install(context.Context, context.IOStream, component.InstallationContext{}) }, context.IOStream, "Installing docker stack"); err != nil { return err } if err := logging.LogOperation(func() error { - return s.Update(context.IOStream, true) + return s.Update(context.Context, context.IOStream, true) }, context.IOStream, "Updating docker stack"); err != nil { return err } diff --git a/cmd/server.go b/cmd/server.go index 56bfc63..e09995d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -5,6 +5,7 @@ import ( wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/internal/cli" + "github.com/FAU-CDI/wisski-distillery/pkg/cancel" "github.com/tkw1536/goprogram/exit" ) @@ -33,7 +34,7 @@ var errServerListen = exit.Error{ func (s server) Run(context wisski_distillery.Context) error { dis := context.Environment - handler, err := dis.Control().Server(dis.Context(), context.IOStream) + handler, err := dis.Control().Server(context.Context, context.IOStream) if err != nil { return err } @@ -47,14 +48,21 @@ func (s server) Run(context wisski_distillery.Context) error { } go func() { - <-dis.Context().Done() + <-context.Context.Done() listener.Close() }() - // and serve that listener - err = http.Serve(listener, http.StripPrefix(s.Prefix, handler)) - if err == nil { - return nil + server := http.Server{ + Handler: http.StripPrefix(s.Prefix, handler), } + + err, _ = cancel.WithContext(context.Context, func(start func()) error { + start() + return server.Serve(listener) + }, func() { + // gracefully shutdown server + context.Printf("shutting down server") + server.Shutdown(context.Context) + }) return errServerListen.Wrap(err) } diff --git a/cmd/shell.go b/cmd/shell.go index 3d2f667..4add972 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -38,12 +38,12 @@ var errShell = exit.Error{ } func (sh shell) Run(context wisski_distillery.Context) error { - instance, err := context.Environment.Instances().WissKI(sh.Positionals.Slug) + instance, err := context.Environment.Instances().WissKI(context.Context, sh.Positionals.Slug) if err != nil { return err } - code, err := instance.Barrel().Shell(context.IOStream, sh.Positionals.Args...) + code, err := instance.Barrel().Shell(context.Context, context.IOStream, sh.Positionals.Args...) if err != nil { return errShell.WithMessageF(err) } diff --git a/cmd/snapshot.go b/cmd/snapshot.go index 0915c0b..20c9971 100644 --- a/cmd/snapshot.go +++ b/cmd/snapshot.go @@ -39,13 +39,13 @@ func (sn snapshot) Run(context wisski_distillery.Context) error { dis := context.Environment // find the instance! - instance, err := dis.Instances().WissKI(sn.Positionals.Slug) + instance, err := dis.Instances().WissKI(context.Context, sn.Positionals.Slug) if err != nil { return err } // do a snapshot of it! - err = dis.Exporter().MakeExport(context.IOStream, exporter.ExportTask{ + err = dis.Exporter().MakeExport(context.Context, context.IOStream, exporter.ExportTask{ Dest: sn.Positionals.Dest, StagingOnly: sn.StagingOnly, diff --git a/cmd/ssh.go b/cmd/ssh.go index 38ee568..d551cf3 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -31,7 +31,7 @@ var errSSHListen = exit.Error{ func (s ssh) Run(context wisski_distillery.Context) error { dis := context.Environment - server, err := dis.SSH().Server(dis.Context(), s.PrivateKeyPath, context.IOStream) + server, err := dis.SSH().Server(context.Context, s.PrivateKeyPath, context.IOStream) if err != nil { return err } @@ -45,7 +45,7 @@ func (s ssh) Run(context wisski_distillery.Context) error { } go func() { - <-dis.Context().Done() + <-context.Context.Done() listener.Close() }() diff --git a/cmd/status.go b/cmd/status.go index 0515f67..5e9093c 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -25,7 +25,7 @@ func (cStatus) Description() wisski_distillery.Description { } func (s cStatus) Run(context wisski_distillery.Context) error { - status, _, err := context.Environment.Info().Status(true) + status, _, err := context.Environment.Info().Status(context.Context, true) if err != nil { return err } diff --git a/cmd/system_pause.go b/cmd/system_pause.go index 1c7955e..eed48d7 100644 --- a/cmd/system_pause.go +++ b/cmd/system_pause.go @@ -65,7 +65,7 @@ func (sp systempause) start(context wisski_distillery.Context, dis *dis.Distille Handler: func(item component.Installable, index int, writer io.Writer) error { io := stream.NewIOStream(writer, writer, stream.Null, 0) - return item.Stack(context.Environment.Environment).Up(io) + return item.Stack(context.Environment.Environment).Up(context.Context, io) }, }, dis.Installable()); err != nil { return err @@ -74,7 +74,7 @@ func (sp systempause) start(context wisski_distillery.Context, dis *dis.Distille logging.LogMessage(context.IOStream, "Starting Up WissKIs") // find the instances - wissKIs, err := dis.Instances().All() + wissKIs, err := dis.Instances().All(context.Context) if err != nil { return err } @@ -88,7 +88,7 @@ func (sp systempause) start(context wisski_distillery.Context, dis *dis.Distille Handler: func(item *wisski.WissKI, index int, writer io.Writer) error { io := stream.NewIOStream(writer, writer, stream.Null, 0) - return item.Barrel().Stack().Up(io) + return item.Barrel().Stack().Up(context.Context, io) }, }, wissKIs); err != nil { return err @@ -101,7 +101,7 @@ func (sp systempause) stop(context wisski_distillery.Context, dis *dis.Distiller logging.LogMessage(context.IOStream, "Shutting Down WissKIs") // find the instances - wissKIs, err := dis.Instances().All() + wissKIs, err := dis.Instances().All(context.Context) if err != nil { return err } @@ -115,7 +115,7 @@ func (sp systempause) stop(context wisski_distillery.Context, dis *dis.Distiller Handler: func(item *wisski.WissKI, index int, writer io.Writer) error { io := stream.NewIOStream(writer, writer, stream.Null, 0) - return item.Barrel().Stack().Down(io) + return item.Barrel().Stack().Down(context.Context, io) }, }, wissKIs); err != nil { return err @@ -132,7 +132,7 @@ func (sp systempause) stop(context wisski_distillery.Context, dis *dis.Distiller Handler: func(item component.Installable, index int, writer io.Writer) error { io := stream.NewIOStream(writer, writer, stream.Null, 0) - return item.Stack(context.Environment.Environment).Down(io) + return item.Stack(context.Environment.Environment).Down(context.Context, io) }, }, dis.Installable()); err != nil { return err diff --git a/cmd/system_update.go b/cmd/system_update.go index 2c6ceb7..2d5e13d 100644 --- a/cmd/system_update.go +++ b/cmd/system_update.go @@ -135,11 +135,11 @@ func (si systemupdate) Run(context wisski_distillery.Context) error { io := stream.NewIOStream(writer, writer, stream.Null, 0) stack := item.Stack(context.Environment.Environment) - if err := stack.Install(io, item.Context(ctx)); err != nil { + if err := stack.Install(context.Context, io, item.Context(ctx)); err != nil { return err } - if err := stack.Update(io, true); err != nil { + if err := stack.Update(context.Context, io, true); err != nil { return err } @@ -154,7 +154,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error { updated[item.ID()] = struct{}{} }() - return ud.Update(io) + return ud.Update(context.Context, io) }, }, dis.Installable()) }, context.IOStream, "Performing Stack Updates"); err != nil { @@ -170,7 +170,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error { context.Println("Already updated") return nil } - return item.Update(context.IOStream) + return item.Update(context.Context, context.IOStream) }, context.IOStream, "Updating Component: %s", name); err != nil { return errBootstrapComponent.WithMessageF(name, err) } @@ -194,9 +194,9 @@ var errMustExecFailed = exit.Error{ func (si systemupdate) mustExec(context wisski_distillery.Context, workdir string, exe string, argv ...string) error { dis := context.Environment if workdir == "" { - workdir = context.Environment.Config.DeployRoot + workdir = dis.Config.DeployRoot } - code := dis.Still.Environment.Exec(context.IOStream, workdir, exe, argv...) + code := dis.Still.Environment.Exec(context.Context, context.IOStream, workdir, exe, argv...) if code != 0 { err := errMustExecFailed.WithMessageF(code) err.ExitCode = exit.ExitCode(code) diff --git a/cmd/update_prefix_config.go b/cmd/update_prefix_config.go index 45f88c9..b4db4ee 100644 --- a/cmd/update_prefix_config.go +++ b/cmd/update_prefix_config.go @@ -37,14 +37,14 @@ var errPrefixUpdateFailed = exit.Error{ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error { dis := context.Environment - wissKIs, err := dis.Instances().All() + wissKIs, err := dis.Instances().All(context.Context) if err != nil { return errPrefixUpdateFailed.Wrap(err) } return status.StreamGroup(context.IOStream, upc.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { io.Println("reading prefixes") - err := instance.Prefixes().Update() + err := instance.Prefixes().Update(context.Context) if err != nil { return errPrefixUpdateFailed.Wrap(err) } diff --git a/go.mod b/go.mod index f9c3a51..6603d02 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,12 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/pkg/errors v0.9.1 - github.com/tkw1536/goprogram v0.1.1 + github.com/tkw1536/goprogram v0.2.0 golang.org/x/crypto v0.3.0 - golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 - golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 - gorm.io/driver/mysql v1.3.6 - gorm.io/gorm v1.23.10 + golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 + golang.org/x/sync v0.1.0 + gorm.io/driver/mysql v1.4.4 + gorm.io/gorm v1.24.2 ) require ( diff --git a/go.sum b/go.sum index 99a1e4b..305234b 100644 --- a/go.sum +++ b/go.sum @@ -31,15 +31,21 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/tkw1536/goprogram v0.1.1 h1:gamK9OuRqoX2yQlA/nkgfVHHZWd/u2uUj6vJMYrYa70= github.com/tkw1536/goprogram v0.1.1/go.mod h1:Jqs0sTMzhrAGCX3JQrlEwQ0WRWQACCvuQQkaBDp65pE= +github.com/tkw1536/goprogram v0.2.0 h1:qoa5Izgq5gfVggkAcOtCwpjz4oZv1KgDEJ8BHIK/djQ= +github.com/tkw1536/goprogram v0.2.0/go.mod h1:Jqs0sTMzhrAGCX3JQrlEwQ0WRWQACCvuQQkaBDp65pE= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 h1:fGZugkZk2UgYBxtpKmvub51Yno1LJDeEsRp2xGD+0gY= golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4= +golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -59,6 +65,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gorm.io/driver/mysql v1.3.6 h1:BhX1Y/RyALb+T9bZ3t07wLnPZBukt+IRkMn8UZSNbGM= gorm.io/driver/mysql v1.3.6/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ= +gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.10 h1:4Ne9ZbzID9GUxRkllxN4WjJKpsHx8YbKvekVdgyWh24= gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0= +gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= diff --git a/internal/dis/component/backup.go b/internal/dis/component/backup.go index 82bf9e2..69fdb0b 100644 --- a/internal/dis/component/backup.go +++ b/internal/dis/component/backup.go @@ -1,6 +1,7 @@ package component import ( + "context" "io" "path/filepath" @@ -45,7 +46,7 @@ type StagingContext interface { // Passing the empty path creates the destination as a directory. // // It then allows op to fill the file. - AddDirectory(path string, op func() error) error + AddDirectory(path string, op func(context.Context) error) error // CopyFile copies a file from src to dst. CopyFile(dst, src string) error @@ -61,12 +62,13 @@ type StagingContext interface { // The op function must not retain file. // The underlying file does not need to be closed. // AddFile will not return before op has returned. - AddFile(path string, op func(file io.Writer) error) error + AddFile(path string, op func(ctx context.Context, file io.Writer) error) error } // NewStagingContext returns a new [StagingContext] -func NewStagingContext(env environment.Environment, io stream.IOStream, path string, manifest chan<- string) StagingContext { +func NewStagingContext(ctx context.Context, env environment.Environment, io stream.IOStream, path string, manifest chan<- string) StagingContext { return &stagingContext{ + ctx: ctx, env: env, io: io, path: path, @@ -76,6 +78,7 @@ func NewStagingContext(env environment.Environment, io stream.IOStream, path str // stagingContext implements [components.StagingContext] type stagingContext struct { + ctx context.Context env environment.Environment // environment io stream.IOStream // context the files are sent to path string // path to send files to @@ -110,7 +113,12 @@ func (bc *stagingContext) resolve(path string) (dest string, err error) { return filepath.Join(bc.path, path), nil } -func (sc *stagingContext) AddDirectory(path string, op func() error) error { +func (sc *stagingContext) AddDirectory(path string, op func(context.Context) error) error { + // check if we are already done + if err, ok := sc.ctxdone(); ok { + return err + } + // resolve the path! dst, err := sc.resolve(path) if err != nil { @@ -126,30 +134,43 @@ func (sc *stagingContext) AddDirectory(path string, op func() error) error { sc.sendPath(path) // and run the files! - return op() + return op(sc.ctx) } func (sc *stagingContext) CopyFile(dst, src string) error { + if err, ok := sc.ctxdone(); ok { + return err + } + dstPath, err := sc.resolve(dst) if err != nil { return err } sc.sendPath(dst) - return fsx.CopyFile(sc.env, dstPath, src) + return fsx.CopyFile(sc.ctx, sc.env, dstPath, src) } func (sc *stagingContext) CopyDirectory(dst, src string) error { + if err, ok := sc.ctxdone(); ok { + return err + } + dstPath, err := sc.resolve(dst) if err != nil { return err } - return fsx.CopyDirectory(sc.env, dstPath, src, func(dst, src string) { + return fsx.CopyDirectory(sc.ctx, sc.env, dstPath, src, func(dst, src string) { sc.sendPath(dst) }) } -func (sc *stagingContext) AddFile(path string, op func(file io.Writer) error) error { +func (sc *stagingContext) AddFile(path string, op func(ctx context.Context, file io.Writer) error) error { + // check if we're already done + if err, ok := sc.ctxdone(); ok { + return err + } + // resolve the path! dst, err := sc.resolve(path) if err != nil { @@ -167,5 +188,11 @@ func (sc *stagingContext) AddFile(path string, op func(file io.Writer) error) er sc.sendPath(path) // and do whatever they wanted to do - return op(file) + return op(sc.ctx, file) +} + +func (sc *stagingContext) ctxdone() (err error, done bool) { + err = sc.ctx.Err() + done = (err != nil) + return } diff --git a/internal/dis/component/control/home/home.go b/internal/dis/component/control/home/home.go index 636c4b5..b338488 100644 --- a/internal/dis/component/control/home/home.go +++ b/internal/dis/component/control/home/home.go @@ -26,10 +26,10 @@ type Home struct { func (*Home) Routes() []string { return []string{"/"} } -func (home *Home) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) { - home.updateRedirect(context, io) - home.updateInstances(context, io) - home.updateRender(context, io) +func (home *Home) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) { + home.updateRedirect(ctx, io) + home.updateInstances(ctx, io) + home.updateRender(ctx, io) return home, nil } diff --git a/internal/dis/component/control/home/public.go b/internal/dis/component/control/home/public.go index e1186bc..57802a7 100644 --- a/internal/dis/component/control/home/public.go +++ b/internal/dis/component/control/home/public.go @@ -19,14 +19,27 @@ func (home *Home) updateInstances(ctx context.Context, io stream.IOStream) { for t := range timex.TickContext(ctx, home.RefreshInterval) { io.Printf("[%s]: reloading instance list\n", t.Format(time.Stamp)) - names, _ := home.instanceMap() - home.instanceNames.Set(names) + err := (func() error { + ctx, cancel := context.WithTimeout(ctx, home.RefreshInterval) + defer cancel() + + names, err := home.instanceMap(ctx) + if err != nil { + return err + } + + home.instanceNames.Set(names) + return nil + })() + if err != nil { + io.EPrintf("error reloading instances: ", err.Error()) + } } }() } -func (home *Home) instanceMap() (map[string]struct{}, error) { - wissKIs, err := home.Instances.All() +func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) { + wissKIs, err := home.Instances.All(ctx) if err != nil { return nil, err } @@ -41,10 +54,23 @@ func (home *Home) instanceMap() (map[string]struct{}, error) { func (home *Home) updateRender(ctx context.Context, io stream.IOStream) { go func() { for t := range timex.TickContext(ctx, home.RefreshInterval) { - io.Printf("[%s]: reloading home render\n", t.Format(time.Stamp)) + io.Printf("[%s]: reloading home render list\n", t.Format(time.Stamp)) - bytes, _ := home.homeRender() - home.homeBytes.Set(bytes) + err := (func() error { + ctx, cancel := context.WithTimeout(ctx, home.RefreshInterval) + defer cancel() + + bytes, err := home.homeRender(ctx) + if err != nil { + return err + } + + home.homeBytes.Set(bytes) + return nil + })() + if err != nil { + io.EPrintf("error reloading instances: ", err.Error()) + } } }() } @@ -53,7 +79,7 @@ func (home *Home) updateRender(ctx context.Context, io stream.IOStream) { var homeHTMLStr string var homeTemplate = static.AssetsHomeHome.MustParseShared("home.html", homeHTMLStr) -func (home *Home) homeRender() ([]byte, error) { +func (home *Home) homeRender(ctx context.Context) ([]byte, error) { var context HomeContext // setup a couple of static things @@ -61,7 +87,7 @@ func (home *Home) homeRender() ([]byte, error) { context.SelfRedirect = home.Config.SelfRedirect.String() // find all the WissKIs - wissKIs, err := home.Instances.All() + wissKIs, err := home.Instances.All(ctx) if err != nil { return nil, err } @@ -73,7 +99,7 @@ func (home *Home) homeRender() ([]byte, error) { i := i wissKI := instance eg.Go(func() (err error) { - context.Instances[i], err = wissKI.Info().Information(false) + context.Instances[i], err = wissKI.Info().Information(ctx, false) return }) } diff --git a/internal/dis/component/control/home/redirect.go b/internal/dis/component/control/home/redirect.go index 38d1f0f..ac6aad2 100644 --- a/internal/dis/component/control/home/redirect.go +++ b/internal/dis/component/control/home/redirect.go @@ -16,13 +16,27 @@ func (home *Home) updateRedirect(ctx context.Context, io stream.IOStream) { for t := range timex.TickContext(ctx, home.RefreshInterval) { io.Printf("[%s]: reloading overrides\n", t.Format(time.Stamp)) - redirect, _ := home.loadRedirect() - home.redirect.Set(&redirect) + err := (func() error { + ctx, cancel := context.WithTimeout(ctx, home.RefreshInterval) + defer cancel() + + redirect, err := home.loadRedirect(ctx) + if err != nil { + return err + } + + home.redirect.Set(&redirect) + return nil + })() + if err != nil { + io.EPrintf("error reloading overrides: ", err.Error()) + } + } }() } -func (home *Home) loadRedirect() (redirect Redirect, err error) { +func (home *Home) loadRedirect(ctx context.Context) (redirect Redirect, err error) { if redirect.Overrides == nil { redirect.Overrides = make(map[string]string) } diff --git a/internal/dis/component/control/info/components.go b/internal/dis/component/control/info/components.go index 2e3bf1e..20db575 100644 --- a/internal/dis/component/control/info/components.go +++ b/internal/dis/component/control/info/components.go @@ -52,7 +52,7 @@ func (info *Info) ingredients(r *http.Request) (cp ingredientsContext, err error cp.Time = time.Now().UTC() // find the instance itself! - instance, err := info.Instances.WissKI(mux.Vars(r)["slug"]) + instance, err := info.Instances.WissKI(r.Context(), mux.Vars(r)["slug"]) if err == instances.ErrWissKINotFound { return cp, httpx.ErrNotFound } diff --git a/internal/dis/component/control/info/index.go b/internal/dis/component/control/info/index.go index c65e6ad..1188284 100644 --- a/internal/dis/component/control/info/index.go +++ b/internal/dis/component/control/info/index.go @@ -1,6 +1,7 @@ package info import ( + "context" "net/http" "time" @@ -25,18 +26,18 @@ type indexContext struct { } func (info *Info) index(r *http.Request) (idx indexContext, err error) { - idx.Distillery, idx.Instances, err = info.Status(true) + idx.Distillery, idx.Instances, err = info.Status(r.Context(), true) return } // Status produces a new observation of the distillery, and a new information of all instances // The information on all instances is passed the given quick flag. -func (info *Info) Status(QuickInformation bool) (target status.Distillery, information []status.WissKI, err error) { +func (info *Info) Status(ctx context.Context, QuickInformation bool) (target status.Distillery, information []status.WissKI, err error) { var group errgroup.Group group.Go(func() error { // list all the instances - all, err := info.Instances.All() + all, err := info.Instances.All(ctx) if err != nil { return err } @@ -50,7 +51,7 @@ func (info *Info) Status(QuickInformation bool) (target status.Distillery, infor // store the info for this group! group.Go(func() (err error) { - information[i], err = instance.Info().Information(true) + information[i], err = instance.Info().Information(ctx, true) return err }) } @@ -59,7 +60,9 @@ func (info *Info) Status(QuickInformation bool) (target status.Distillery, infor }) // gather all the observations - var flags component.FetcherFlags + flags := component.FetcherFlags{ + Context: ctx, + } for _, o := range info.Fetchers { o := o group.Go(func() error { diff --git a/internal/dis/component/control/info/info.go b/internal/dis/component/control/info/info.go index 4ced25e..4d242d9 100644 --- a/internal/dis/component/control/info/info.go +++ b/internal/dis/component/control/info/info.go @@ -28,11 +28,11 @@ type Info struct { func (*Info) Routes() []string { return []string{"/dis/"} } -func (info *Info) Handler(route string, context context.Context, io stream.IOStream) (handler http.Handler, err error) { +func (info *Info) Handler(ctx context.Context, route string, io stream.IOStream) (handler http.Handler, err error) { router := mux.NewRouter() { socket := &httpx.WebSocket{ - Context: context, + Context: ctx, Fallback: router, Handler: info.serveSocket, } @@ -82,12 +82,12 @@ func (info *Info) Handler(route string, context context.Context, io stream.IOStr } // get the instance - instance, err := info.Instances.WissKI(r.PostFormValue("slug")) + instance, err := info.Instances.WissKI(r.Context(), r.PostFormValue("slug")) if err != nil { return "", httpx.ErrNotFound } - target, err := instance.Users().Login(nil, r.PostFormValue("user")) + target, err := instance.Users().Login(r.Context(), nil, r.PostFormValue("user")) if err != nil { return "", err } diff --git a/internal/dis/component/control/info/instance.go b/internal/dis/component/control/info/instance.go index 5d0ab4b..cd6a303 100644 --- a/internal/dis/component/control/info/instance.go +++ b/internal/dis/component/control/info/instance.go @@ -29,7 +29,7 @@ type instanceContext struct { func (info *Info) instance(r *http.Request) (is instanceContext, err error) { // find the instance itself! - instance, err := info.Instances.WissKI(mux.Vars(r)["slug"]) + instance, err := info.Instances.WissKI(r.Context(), mux.Vars(r)["slug"]) if err == instances.ErrWissKINotFound { return is, httpx.ErrNotFound } @@ -39,7 +39,7 @@ func (info *Info) instance(r *http.Request) (is instanceContext, err error) { is.Instance = instance.Instance // get some more info about the wisski - is.Info, err = instance.Info().Information(false) + is.Info, err = instance.Info().Information(r.Context(), false) if err != nil { return is, err } diff --git a/internal/dis/component/control/info/socket.go b/internal/dis/component/control/info/socket.go index 6ae16ad..cde427a 100644 --- a/internal/dis/component/control/info/socket.go +++ b/internal/dis/component/control/info/socket.go @@ -1,6 +1,7 @@ package info import ( + "context" "encoding/json" "time" @@ -14,14 +15,15 @@ import ( type InstanceAction struct { NumParams int - HandleInteractive func(info *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error - HandleResult func(info *Info, instance *wisski.WissKI, params ...string) (value any, err error) + HandleInteractive func(ctx context.Context, info *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error + HandleResult func(ctx context.Context, info *Info, instance *wisski.WissKI, params ...string) (value any, err error) } var socketInstanceActions = map[string]InstanceAction{ "snapshot": { - HandleInteractive: func(info *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { + HandleInteractive: func(ctx context.Context, info *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { return info.Exporter.MakeExport( + ctx, str, exporter.ExportTask{ Dest: "", @@ -33,18 +35,18 @@ var socketInstanceActions = map[string]InstanceAction{ }, }, "rebuild": { - HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { - return instance.Barrel().Build(str, true) + HandleInteractive: func(ctx context.Context, _ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { + return instance.Barrel().Build(ctx, str, true) }, }, "update": { - HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { - return instance.Drush().Update(str) + HandleInteractive: func(ctx context.Context, _ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { + return instance.Drush().Update(ctx, str) }, }, "cron": { - HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { - return instance.Drush().Cron(str) + HandleInteractive: func(ctx context.Context, _ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { + return instance.Drush().Cron(ctx, str) }, }, } @@ -75,7 +77,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In } // resolve the instance - instance, err := info.Instances.WissKI(string(slug.Bytes)) + instance, err := info.Instances.WissKI(conn.Context(), string(slug.Bytes)) if err != nil { <-conn.WriteText("Instance not found") return @@ -110,7 +112,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In // handle the interactive action if action.HandleInteractive != nil { - err := action.HandleInteractive(info, instance, str, params...) + err := action.HandleInteractive(conn.Context(), info, instance, str, params...) if err != nil { str.EPrintln(err) return @@ -120,7 +122,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In // handle the result computation if action.HandleResult != nil { - result, err := action.HandleResult(info, instance, params...) + result, err := action.HandleResult(conn.Context(), info, instance, params...) if err != nil { str.Println("false") return diff --git a/internal/dis/component/control/server.go b/internal/dis/component/control/server.go index a00ea8f..cd76650 100644 --- a/internal/dis/component/control/server.go +++ b/internal/dis/component/control/server.go @@ -11,7 +11,7 @@ import ( // The server may spawn background tasks, but these should be terminated once context closes. // // Logging messages are directed to io. -func (control *Control) Server(context context.Context, io stream.IOStream) (*http.ServeMux, error) { +func (control *Control) Server(ctx context.Context, io stream.IOStream) (*http.ServeMux, error) { // create a new mux mux := http.NewServeMux() @@ -19,7 +19,7 @@ func (control *Control) Server(context context.Context, io stream.IOStream) (*ht for _, s := range control.Servables { for _, route := range s.Routes() { io.Printf("mounting %s\n", route) - handler, err := s.Handler(route, context, io) + handler, err := s.Handler(ctx, route, io) if err != nil { return nil, err } diff --git a/internal/dis/component/control/static/static.go b/internal/dis/component/control/static/static.go index 99a91bf..6899ca1 100644 --- a/internal/dis/component/control/static/static.go +++ b/internal/dis/component/control/static/static.go @@ -20,7 +20,7 @@ func (*Static) Routes() []string { return []string{"/static/"} } //go:embed dist var staticFS embed.FS -func (static *Static) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) { +func (static *Static) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) { // take the filesystem fs, err := fs.Sub(staticFS, "dist") if err != nil { diff --git a/internal/dis/component/exporter/backup.go b/internal/dis/component/exporter/backup.go index 46597d7..68044e9 100644 --- a/internal/dis/component/exporter/backup.go +++ b/internal/dis/component/exporter/backup.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "fmt" "io" "path/filepath" @@ -49,7 +50,7 @@ type BackupDescription struct { } // New create a new Backup -func (exporter *Exporter) NewBackup(io stream.IOStream, description BackupDescription) (backup Backup) { +func (exporter *Exporter) NewBackup(ctx context.Context, io stream.IOStream, description BackupDescription) (backup Backup) { backup.Description = description // catch anything critical that happened during the snapshot @@ -60,7 +61,7 @@ func (exporter *Exporter) NewBackup(io stream.IOStream, description BackupDescri // do the create keeping track of time! logging.LogOperation(func() error { backup.StartTime = time.Now().UTC() - backup.run(io, exporter) + backup.run(ctx, io, exporter) backup.EndTime = time.Now().UTC() return nil @@ -69,7 +70,7 @@ func (exporter *Exporter) NewBackup(io stream.IOStream, description BackupDescri return } -func (backup *Backup) run(ios stream.IOStream, exporter *Exporter) { +func (backup *Backup) run(ctx context.Context, ios stream.IOStream, exporter *Exporter) { // create a manifest manifest, done := backup.handleManifest(backup.Description.Dest) defer done() @@ -93,6 +94,7 @@ func (backup *Backup) run(ios stream.IOStream, exporter *Exporter) { Handler: func(bc component.Backupable, index int, writer io.Writer) error { return bc.Backup( component.NewStagingContext( + ctx, exporter.Environment, stream.NewIOStream(writer, writer, nil, 0), filepath.Join(backup.Description.Dest, bc.BackupName()), @@ -124,7 +126,7 @@ func (backup *Backup) run(ios stream.IOStream, exporter *Exporter) { } // list all instances - wissKIs, err := exporter.Instances.All() + wissKIs, err := exporter.Instances.All(ctx) if err != nil { backup.InstanceListErr = err return nil @@ -147,7 +149,7 @@ func (backup *Backup) run(ios stream.IOStream, exporter *Exporter) { manifest <- dir - return exporter.NewSnapshot(instance, stream.NewIOStream(writer, writer, nil, 0), SnapshotDescription{ + return exporter.NewSnapshot(ctx, instance, stream.NewIOStream(writer, writer, nil, 0), SnapshotDescription{ Dest: dir, }) }, diff --git a/internal/dis/component/exporter/extras_bookkeeping.go b/internal/dis/component/exporter/extras_bookkeeping.go index 026df1b..7f792bc 100644 --- a/internal/dis/component/exporter/extras_bookkeeping.go +++ b/internal/dis/component/exporter/extras_bookkeeping.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "fmt" "io" @@ -19,8 +20,8 @@ func (Bookkeeping) SnapshotNeedsRunning() bool { return false } func (Bookkeeping) SnapshotName() string { return "bookkeeping.txt" } // Snapshot creates a snapshot of this instance -func (*Bookkeeping) Snapshot(wisski models.Instance, context component.StagingContext) error { - return context.AddFile(".", func(file io.Writer) error { +func (*Bookkeeping) Snapshot(wisski models.Instance, scontext component.StagingContext) error { + return scontext.AddFile(".", func(ctx context.Context, file io.Writer) error { _, err := fmt.Fprintf(file, "%#v\n", wisski) return err }) diff --git a/internal/dis/component/exporter/extras_config.go b/internal/dis/component/exporter/extras_config.go index 2ba25df..4387da1 100644 --- a/internal/dis/component/exporter/extras_config.go +++ b/internal/dis/component/exporter/extras_config.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "path/filepath" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" @@ -15,13 +16,13 @@ func (*Config) BackupName() string { return "config" } -func (control *Config) Backup(context component.StagingContext) error { +func (control *Config) Backup(scontext component.StagingContext) error { files := control.backupFiles() - return context.AddDirectory("", func() error { + return scontext.AddDirectory("", func(ctx context.Context) error { for _, src := range files { name := filepath.Base(src) - if err := context.CopyFile(name, src); err != nil { + if err := scontext.CopyFile(name, src); err != nil { return err } } diff --git a/internal/dis/component/exporter/extras_pathbuilders.go b/internal/dis/component/exporter/extras_pathbuilders.go index 35c1297..315ae85 100644 --- a/internal/dis/component/exporter/extras_pathbuilders.go +++ b/internal/dis/component/exporter/extras_pathbuilders.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "io" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" @@ -17,15 +18,15 @@ func (Pathbuilders) SnapshotNeedsRunning() bool { return true } func (Pathbuilders) SnapshotName() string { return "pathbuilders" } -func (pbs *Pathbuilders) Snapshot(wisski models.Instance, context component.StagingContext) error { - return context.AddDirectory(".", func() error { - builders, err := pbs.Instances.Instance(wisski).Pathbuilder().GetAll(nil) +func (pbs *Pathbuilders) Snapshot(wisski models.Instance, scontext component.StagingContext) error { + return scontext.AddDirectory(".", func(ctx context.Context) error { + builders, err := pbs.Instances.Instance(ctx, wisski).Pathbuilder().GetAll(ctx, nil) if err != nil { return err } for name, bytes := range builders { - if err := context.AddFile(name+".xml", func(file io.Writer) error { + if err := scontext.AddFile(name+".xml", func(ctx context.Context, file io.Writer) error { _, err := file.Write([]byte(bytes)) return err }); err != nil { diff --git a/internal/dis/component/exporter/iface.go b/internal/dis/component/exporter/iface.go index 37fa4c6..d04b29a 100644 --- a/internal/dis/component/exporter/iface.go +++ b/internal/dis/component/exporter/iface.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "io" "path/filepath" @@ -42,7 +43,7 @@ type export interface { // MakeExport performs an export task as described by flags. // Output is directed to the provided io. -func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err error) { +func (exporter *Exporter) MakeExport(ctx context.Context, io stream.IOStream, task ExportTask) (err error) { // extract parameters Title := "Backup" Slug := "" @@ -95,11 +96,11 @@ func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err e var sl export if task.Instance == nil { task.BackupDescription.Dest = stagingDir - backup := exporter.NewBackup(io, task.BackupDescription) + backup := exporter.NewBackup(ctx, io, task.BackupDescription) sl = &backup } else { task.SnapshotDescription.Dest = stagingDir - snapshot := exporter.NewSnapshot(task.Instance, io, task.SnapshotDescription) + snapshot := exporter.NewSnapshot(ctx, task.Instance, io, task.SnapshotDescription) sl = &snapshot } @@ -131,7 +132,7 @@ func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err e // write out the log entry entry.Path = stagingDir entry.Packed = false - exporter.ExporterLogger.Add(entry) + exporter.ExporterLogger.Add(ctx, entry) io.Printf("Wrote %s\n", stagingDir) return nil @@ -159,7 +160,7 @@ func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err e logging.LogMessage(io, "Writing Log Entry") entry.Path = archivePath entry.Packed = true - exporter.ExporterLogger.Add(entry) + exporter.ExporterLogger.Add(ctx, entry) // and we're done! return nil diff --git a/internal/dis/component/exporter/logger/logger.go b/internal/dis/component/exporter/logger/logger.go index 7482cc5..805698a 100644 --- a/internal/dis/component/exporter/logger/logger.go +++ b/internal/dis/component/exporter/logger/logger.go @@ -1,6 +1,8 @@ package logger import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/models" @@ -19,8 +21,8 @@ type Logger struct { // For retrieves (and prunes) the ExportLog. // Slug determines if entries for Backups (empty slug) // or a specific Instance (non-empty slug) are returned. -func (log *Logger) For(slug string) (exports []models.Export, err error) { - exports, err = log.Log() +func (log *Logger) For(ctx context.Context, slug string) (exports []models.Export, err error) { + exports, err = log.Log(ctx) if err != nil { return nil, err } @@ -31,9 +33,9 @@ func (log *Logger) For(slug string) (exports []models.Export, err error) { } // Log retrieves (and prunes) all entries in the snapshot log. -func (log *Logger) Log() ([]models.Export, error) { +func (log *Logger) Log(ctx context.Context) ([]models.Export, error) { // query the table! - table, err := log.SQL.QueryTable(false, models.ExportTable) + table, err := log.SQL.QueryTable(ctx, false, models.ExportTable) if err != nil { return nil, err } @@ -63,9 +65,9 @@ func (log *Logger) Log() ([]models.Export, error) { } // AddToExportLog adds the provided export to the log. -func (log *Logger) Add(export models.Export) error { +func (log *Logger) Add(ctx context.Context, export models.Export) error { // find the table - table, err := log.SQL.QueryTable(false, models.ExportTable) + table, err := log.SQL.QueryTable(ctx, false, models.ExportTable) if err != nil { return err } @@ -79,7 +81,7 @@ func (log *Logger) Add(export models.Export) error { } // Fetch writes the SnapshotLog into the given observation -func (logger *Logger) Fetch(flags component.FetcherFlags, target *status.Distillery) (err error) { - target.Backups, err = logger.For("") +func (logger *Logger) Fetch(ctx context.Context, flags component.FetcherFlags, target *status.Distillery) (err error) { + target.Backups, err = logger.For(ctx, "") return } diff --git a/internal/dis/component/exporter/prune.go b/internal/dis/component/exporter/prune.go index 120987f..dabae60 100644 --- a/internal/dis/component/exporter/prune.go +++ b/internal/dis/component/exporter/prune.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "path/filepath" "time" @@ -14,7 +15,7 @@ func (exporter *Exporter) ShouldPrune(modtime time.Time) bool { } // Prune prunes all old exports -func (exporter *Exporter) PruneExports(io stream.IOStream) error { +func (exporter *Exporter) PruneExports(ctx context.Context, io stream.IOStream) error { sPath := exporter.ArchivePath() // list all the files @@ -50,6 +51,6 @@ func (exporter *Exporter) PruneExports(io stream.IOStream) error { } // prune the snapshot log! - _, err = exporter.ExporterLogger.Log() + _, err = exporter.ExporterLogger.Log(ctx) return err } diff --git a/internal/dis/component/exporter/snapshot.go b/internal/dis/component/exporter/snapshot.go index b5442e7..5e88bc0 100644 --- a/internal/dis/component/exporter/snapshot.go +++ b/internal/dis/component/exporter/snapshot.go @@ -1,6 +1,7 @@ package exporter import ( + "context" "fmt" "io" "path/filepath" @@ -44,10 +45,10 @@ type Snapshot struct { } // Snapshot creates a new snapshot of this instance into dest -func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) { +func (snapshots *Exporter) NewSnapshot(ctx context.Context, instance *wisski.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) { logging.LogMessage(io, "Locking instance") - if !instance.Locker().TryLock() { + if !instance.Locker().TryLock(ctx) { err := locker.Locked io.EPrintln(err) logging.LogMessage(io, "Aborting snapshot creation") @@ -58,7 +59,7 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre } defer func() { logging.LogMessage(io, "Unlocking instance") - instance.Locker().Unlock() + instance.Locker().Unlock(ctx) }() // setup the snapshot @@ -74,8 +75,8 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre 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.ErrWhitebox = snapshot.makeParts(ctx, io, snapshots, instance, false) + snapshot.ErrBlackbox = snapshot.makeParts(ctx, io, snapshots, instance, true) snapshot.EndTime = time.Now().UTC() return nil @@ -85,16 +86,16 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre return } -func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Exporter, instance *wisski.WissKI, needsRunning bool) map[string]error { +func (snapshot *Snapshot) makeParts(ctx context.Context, ios stream.IOStream, snapshots *Exporter, instance *wisski.WissKI, needsRunning bool) map[string]error { if !needsRunning && !snapshot.Description.Keepalive { stack := instance.Barrel().Stack() logging.LogMessage(ios, "Stopping instance") - snapshot.ErrStop = stack.Down(ios) + snapshot.ErrStop = stack.Down(ctx, ios) defer func() { logging.LogMessage(ios, "Starting instance") - snapshot.ErrStart = stack.Up(ios) + snapshot.ErrStart = stack.Up(ctx, ios) }() } // handle writing the manifest! @@ -123,6 +124,7 @@ func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Exporter, in return sc.Snapshot( instance.Instance, component.NewStagingContext( + ctx, snapshots.Environment, stream.NewIOStream(writer, writer, nil, 0), filepath.Join(snapshot.Description.Dest, sc.SnapshotName()), diff --git a/internal/dis/component/fetcher.go b/internal/dis/component/fetcher.go index 313e26f..1a9f9a7 100644 --- a/internal/dis/component/fetcher.go +++ b/internal/dis/component/fetcher.go @@ -1,6 +1,10 @@ package component -import "github.com/FAU-CDI/wisski-distillery/internal/status" +import ( + "context" + + "github.com/FAU-CDI/wisski-distillery/internal/status" +) type DistilleryFetcher interface { Component @@ -11,4 +15,6 @@ type DistilleryFetcher interface { } // FetcherFlags describes options for a DistilleryFetcher -type FetcherFlags struct{} +type FetcherFlags struct { + Context context.Context +} diff --git a/internal/dis/component/installable.go b/internal/dis/component/installable.go index 33762f5..ef691b0 100644 --- a/internal/dis/component/installable.go +++ b/internal/dis/component/installable.go @@ -1,6 +1,8 @@ package component import ( + "context" + "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/tkw1536/goprogram/stream" ) @@ -40,5 +42,5 @@ type Updatable interface { // It may send output to the provided stream. // // Updating should be idempotent, meaning running it multiple times must not break the existing system. - Update(stream stream.IOStream) error + Update(ctx context.Context, stream stream.IOStream) error } diff --git a/internal/dis/component/instances/instances.go b/internal/dis/component/instances/instances.go index e80f62f..cda119c 100644 --- a/internal/dis/component/instances/instances.go +++ b/internal/dis/component/instances/instances.go @@ -1,6 +1,7 @@ package instances import ( + "context" "errors" "path/filepath" @@ -42,13 +43,13 @@ func (instances *Instances) use(wisski *wisski.WissKI) { // WissKI returns the WissKI with the provided slug, if it exists. // It the WissKI does not exist, returns ErrWissKINotFound. -func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err error) { +func (instances *Instances) WissKI(ctx context.Context, slug string) (wissKI *wisski.WissKI, err error) { sql := instances.SQL - if err := sql.WaitQueryTable(); err != nil { + if err := sql.WaitQueryTable(ctx); err != nil { return nil, err } - table, err := sql.QueryTable(false, models.InstanceTable) + table, err := sql.QueryTable(ctx, false, models.InstanceTable) if err != nil { return nil, err } @@ -72,8 +73,8 @@ func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err erro // Instance is a convenience function to return an instance based on a model slug. // When the instance does not exist, returns nil. -func (instances *Instances) Instance(instance models.Instance) *wisski.WissKI { - wissKI, err := instances.WissKI(instance.Slug) +func (instances *Instances) Instance(ctx context.Context, instance models.Instance) *wisski.WissKI { + wissKI, err := instances.WissKI(ctx, instance.Slug) if err != nil { return nil } @@ -82,13 +83,13 @@ func (instances *Instances) Instance(instance models.Instance) *wisski.WissKI { // Has checks if a WissKI with the provided slug exists inside the database. // It does not perform any checks on the WissKI itself. -func (instances *Instances) Has(slug string) (ok bool, err error) { +func (instances *Instances) Has(ctx context.Context, slug string) (ok bool, err error) { sql := instances.SQL - if err := sql.WaitQueryTable(); err != nil { + if err := sql.WaitQueryTable(ctx); err != nil { return false, err } - table, err := sql.QueryTable(false, models.InstanceTable) + table, err := sql.QueryTable(ctx, false, models.InstanceTable) if err != nil { return false, err } @@ -103,37 +104,37 @@ func (instances *Instances) Has(slug string) (ok bool, err error) { // All returns all instances of the WissKI Distillery in consistent order. // // There is no guarantee that this order remains identical between different api releases; however subsequent invocations are guaranteed to return the same order. -func (instances *Instances) All() ([]*wisski.WissKI, error) { - return instances.find(true, func(table *gorm.DB) *gorm.DB { +func (instances *Instances) All(ctx context.Context) ([]*wisski.WissKI, error) { + return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB { return table }) } // WissKIs returns the WissKI instances with the provides slugs. // If a slug does not exist, it is omitted from the result. -func (instances *Instances) WissKIs(slugs ...string) ([]*wisski.WissKI, error) { - return instances.find(true, func(table *gorm.DB) *gorm.DB { +func (instances *Instances) WissKIs(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) { + return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB { return table.Where("slug IN ?", slugs) }) } // Load is like All, except that when no slugs are provided, it calls All. -func (instances *Instances) Load(slugs ...string) ([]*wisski.WissKI, error) { +func (instances *Instances) Load(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) { if len(slugs) == 0 { - return instances.All() + return instances.All(ctx) } - return instances.WissKIs(slugs...) + return instances.WissKIs(ctx, slugs...) } // find finds instances based on the provided query -func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) { +func (instances *Instances) find(ctx context.Context, order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) { sql := instances.SQL - if err := sql.WaitQueryTable(); err != nil { + if err := sql.WaitQueryTable(ctx); err != nil { return nil, err } // open the bookkeeping table - table, err := sql.QueryTable(false, models.InstanceTable) + table, err := sql.QueryTable(ctx, false, models.InstanceTable) if err != nil { return nil, err } diff --git a/internal/dis/component/instances/runtime.go b/internal/dis/component/instances/runtime.go index 554db5c..96f6864 100644 --- a/internal/dis/component/instances/runtime.go +++ b/internal/dis/component/instances/runtime.go @@ -1,6 +1,7 @@ package instances import ( + "context" "embed" "github.com/FAU-CDI/wisski-distillery/pkg/unpack" @@ -19,7 +20,7 @@ var errBootstrapFailedRuntime = exit.Error{ var runtimeResources embed.FS // Update installs or updates runtime components needed by this component. -func (instances *Instances) Update(stream stream.IOStream) error { +func (instances *Instances) Update(ctx context.Context, stream stream.IOStream) error { err := unpack.InstallDir(instances.Still.Environment, instances.Config.RuntimeDir(), "runtime", runtimeResources, func(dst, src string) { stream.Printf("[copy] %s\n", dst) }) diff --git a/internal/dis/component/meta/provision.go b/internal/dis/component/meta/provision.go index e05e304..1abe63b 100644 --- a/internal/dis/component/meta/provision.go +++ b/internal/dis/component/meta/provision.go @@ -1,14 +1,18 @@ package meta -import "github.com/FAU-CDI/wisski-distillery/internal/models" +import ( + "context" + + "github.com/FAU-CDI/wisski-distillery/internal/models" +) // Provision provisions new meta storage for this instance. // NOTE(twiesing): This is a no-op, because we implement Purge. -func (meta *Meta) Provision(instance models.Instance, domain string) error { +func (meta *Meta) Provision(ctx context.Context, instance models.Instance, domain string) error { return nil } // Purge purges the storage for the given instance. -func (meta *Meta) Purge(instance models.Instance, domain string) error { - return meta.Storage(instance.Slug).Purge() +func (meta *Meta) Purge(ctx context.Context, instance models.Instance, domain string) error { + return meta.Storage(instance.Slug).Purge(ctx) } diff --git a/internal/dis/component/meta/storage.go b/internal/dis/component/meta/storage.go index 095603b..c0a5321 100644 --- a/internal/dis/component/meta/storage.go +++ b/internal/dis/component/meta/storage.go @@ -1,6 +1,7 @@ package meta import ( + "context" "encoding/json" "errors" @@ -24,8 +25,8 @@ type Storage struct { // Get retrieves metadata with the provided key and deserializes the first one into target. // If no metadatum exists, returns [ErrMetadatumNotSet]. -func (s Storage) Get(key Key, target any) error { - table, err := s.sql.QueryTable(true, models.MetadataTable) +func (s Storage) Get(ctx context.Context, key Key, target any) error { + table, err := s.sql.QueryTable(ctx, true, models.MetadataTable) if err != nil { return err } @@ -53,8 +54,8 @@ func (s Storage) Get(key Key, target any) error { // The function is intended to return a target for deserialization. // // When no metadatum exists, targets is not called, and nil error is returned. -func (s Storage) GetAll(key Key, target func(index, total int) any) error { - table, err := s.sql.QueryTable(true, models.MetadataTable) +func (s Storage) GetAll(ctx context.Context, key Key, target func(index, total int) any) error { + table, err := s.sql.QueryTable(ctx, true, models.MetadataTable) if err != nil { return err } @@ -80,8 +81,8 @@ func (s Storage) GetAll(key Key, target func(index, total int) any) error { } // Delete deletes all metadata with the provided key. -func (s Storage) Delete(key Key) error { - table, err := s.sql.QueryTable(true, models.MetadataTable) +func (s Storage) Delete(ctx context.Context, key Key) error { + table, err := s.sql.QueryTable(ctx, true, models.MetadataTable) if err != nil { return err } @@ -96,8 +97,8 @@ func (s Storage) Delete(key Key) error { // Set serializes value and stores it with the provided key. // Any other metadata with the same key is deleted. -func (s Storage) Set(key Key, value any) error { - table, err := s.sql.QueryTable(true, models.MetadataTable) +func (s Storage) Set(ctx context.Context, key Key, value any) error { + table, err := s.sql.QueryTable(ctx, true, models.MetadataTable) if err != nil { return err } @@ -131,8 +132,8 @@ func (s Storage) Set(key Key, value any) error { // Set serializes values and stores them with the provided key. // Any other metadata with the same key is deleted. -func (s Storage) SetAll(key Key, values ...any) error { - table, err := s.sql.QueryTable(true, models.MetadataTable) +func (s Storage) SetAll(ctx context.Context, key Key, values ...any) error { + table, err := s.sql.QueryTable(ctx, true, models.MetadataTable) if err != nil { return err } @@ -165,8 +166,8 @@ func (s Storage) SetAll(key Key, values ...any) error { } // Purge removes all metadata, regardless of key. -func (s Storage) Purge() error { - table, err := s.sql.QueryTable(true, models.MetadataTable) +func (s Storage) Purge(ctx context.Context) error { + table, err := s.sql.QueryTable(ctx, true, models.MetadataTable) if err != nil { return err } @@ -181,22 +182,22 @@ func (s Storage) Purge() error { // TypedKey represents a convenience wrapper for a given with a given value. type TypedKey[Value any] Key -func (f TypedKey[Value]) Get(s *Storage) (value Value, err error) { - err = s.Get(Key(f), &value) +func (f TypedKey[Value]) Get(ctx context.Context, s *Storage) (value Value, err error) { + err = s.Get(ctx, Key(f), &value) return } -func (f TypedKey[Value]) GetOrSet(s *Storage, dflt Value) (value Value, err error) { - value, err = f.Get(s) +func (f TypedKey[Value]) GetOrSet(ctx context.Context, s *Storage, dflt Value) (value Value, err error) { + value, err = f.Get(ctx, s) if err == ErrMetadatumNotSet { value = dflt - err = f.Set(s, value) + err = f.Set(ctx, s, value) } return } -func (f TypedKey[Value]) GetAll(m *Storage) (values []Value, err error) { - err = m.GetAll(Key(f), func(index, total int) any { +func (f TypedKey[Value]) GetAll(ctx context.Context, m *Storage) (values []Value, err error) { + err = m.GetAll(ctx, Key(f), func(index, total int) any { if values == nil { values = make([]Value, total) } @@ -205,14 +206,14 @@ func (f TypedKey[Value]) GetAll(m *Storage) (values []Value, err error) { return values, err } -func (f TypedKey[Value]) Set(m *Storage, value Value) error { - return m.Set(Key(f), value) +func (f TypedKey[Value]) Set(ctx context.Context, m *Storage, value Value) error { + return m.Set(ctx, Key(f), value) } -func (f TypedKey[Value]) SetAll(m *Storage, values ...Value) error { - return m.SetAll(Key(f), collection.AsAny(values)...) +func (f TypedKey[Value]) SetAll(ctx context.Context, m *Storage, values ...Value) error { + return m.SetAll(ctx, Key(f), collection.AsAny(values)...) } -func (f TypedKey[Value]) Delete(m *Storage) error { - return m.Delete(Key(f)) +func (f TypedKey[Value]) Delete(ctx context.Context, m *Storage) error { + return m.Delete(ctx, Key(f)) } diff --git a/internal/dis/component/provision.go b/internal/dis/component/provision.go index eff75fd..98ebaed 100644 --- a/internal/dis/component/provision.go +++ b/internal/dis/component/provision.go @@ -1,6 +1,8 @@ package component import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/models" ) @@ -10,9 +12,9 @@ type Provisionable interface { // Provision provisions resources specific to the provided instance. // Domain holds the full (unique) domain name of the given instance. - Provision(instance models.Instance, domain string) error + Provision(ctx context.Context, instance models.Instance, domain string) error // Purge purges resources specific to the provided instance. // Domain holds the full (unique) domain name of the given instance. - Purge(instance models.Instance, domain string) error + Purge(ctx context.Context, instance models.Instance, domain string) error } diff --git a/internal/dis/component/resolver/prefixes.go b/internal/dis/component/resolver/prefixes.go index 1e90b1a..9b6d0cc 100644 --- a/internal/dis/component/resolver/prefixes.go +++ b/internal/dis/component/resolver/prefixes.go @@ -9,20 +9,34 @@ import ( ) // updatePrefixes starts updating prefixes -func (resolver *Resolver) updatePrefixes(io stream.IOStream, ctx context.Context) { +func (resolver *Resolver) updatePrefixes(ctx context.Context, io stream.IOStream) { go func() { for t := range timex.TickContext(ctx, resolver.RefreshInterval) { io.Printf("[%s]: reloading prefixes\n", t.Format(time.Stamp)) - prefixes, _ := resolver.AllPrefixes() - resolver.prefixes.Set(prefixes) + + err := (func() (err error) { + ctx, cancel := context.WithTimeout(ctx, resolver.RefreshInterval) + defer cancel() + + prefixes, err := resolver.AllPrefixes(ctx) + if err != nil { + return err + } + + resolver.prefixes.Set(prefixes) + return nil + })() + if err != nil { + io.EPrintf("error reloading prefixes: ", err.Error()) + } } }() } // AllPrefixes returns a list of all prefixes from the server. // Prefixes may be cached on the server -func (resolver *Resolver) AllPrefixes() (map[string]string, error) { - instances, err := resolver.Instances.All() +func (resolver *Resolver) AllPrefixes(ctx context.Context) (map[string]string, error) { + instances, err := resolver.Instances.All(ctx) if err != nil { return nil, err } @@ -37,7 +51,7 @@ func (resolver *Resolver) AllPrefixes() (map[string]string, error) { // failed to fetch prefixes for this particular instance // => skip it! - prefixes, err := instance.Prefixes().AllCached() + prefixes, err := instance.Prefixes().AllCached(ctx) if err != nil { lastErr = err continue diff --git a/internal/dis/component/resolver/resolver.go b/internal/dis/component/resolver/resolver.go index 51d019c..e6cccd9 100644 --- a/internal/dis/component/resolver/resolver.go +++ b/internal/dis/component/resolver/resolver.go @@ -28,7 +28,7 @@ type Resolver struct { func (resolver *Resolver) Routes() []string { return []string{"/go/", "/wisski/get/"} } -func (resolver *Resolver) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) { +func (resolver *Resolver) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) { var err error return resolver.handler.Get(func() (p wdresolve.ResolveHandler) { p.TrustXForwardedProto = true @@ -51,7 +51,7 @@ func (resolver *Resolver) Handler(route string, context context.Context, io stre } // start updating prefixes - resolver.updatePrefixes(io, context) + resolver.updatePrefixes(ctx, io) // resolve the prefixes p.Resolver = resolvers.InOrder{ diff --git a/internal/dis/component/server.go b/internal/dis/component/server.go index 62b4a39..c353883 100644 --- a/internal/dis/component/server.go +++ b/internal/dis/component/server.go @@ -15,5 +15,5 @@ type Servable interface { Routes() []string // Handler returns the handler for the requested route - Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) + Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) } diff --git a/internal/dis/component/solr/solr.go b/internal/dis/component/solr/solr.go index ea0a66b..2c23c59 100644 --- a/internal/dis/component/solr/solr.go +++ b/internal/dis/component/solr/solr.go @@ -1,7 +1,6 @@ package solr import ( - "context" "embed" "path/filepath" "time" @@ -15,8 +14,7 @@ type Solr struct { BaseURL string // upstream solr url - PollContext context.Context // context to abort polling with - PollInterval time.Duration // duration to wait for during wait + PollInterval time.Duration // duration to wait for during wait } func (s *Solr) Path() string { diff --git a/internal/dis/component/sql/backup.go b/internal/dis/component/sql/backup.go index e654562..38a6685 100644 --- a/internal/dis/component/sql/backup.go +++ b/internal/dis/component/sql/backup.go @@ -1,6 +1,7 @@ package sql import ( + "context" "errors" "io" @@ -14,10 +15,10 @@ func (*SQL) BackupName() string { } // Backup makes a backup of all SQL databases into the path dest. -func (sql *SQL) Backup(context component.StagingContext) error { - return context.AddFile("", func(file io.Writer) error { - io := context.IO().Streams(file, nil, nil, 0).NonInteractive() - code, err := sql.Stack(sql.Environment).Exec(io, "sql", "mysqldump", "--all-databases") +func (sql *SQL) Backup(scontext component.StagingContext) error { + return scontext.AddFile("", func(ctx context.Context, file io.Writer) error { + io := scontext.IO().Streams(file, nil, nil, 0).NonInteractive() + code, err := sql.Stack(sql.Environment).Exec(ctx, io, "sql", "mysqldump", "--all-databases") if err != nil { return err } diff --git a/internal/dis/component/sql/connect.go b/internal/dis/component/sql/connect.go index 7d63f82..84e7fe4 100644 --- a/internal/dis/component/sql/connect.go +++ b/internal/dis/component/sql/connect.go @@ -40,20 +40,12 @@ func (sql *SQL) Exec(query string, args ...interface{}) error { } } -// WaitExec waits for the query interface to be able to connect to the database -func (sql *SQL) WaitExec() error { - return timex.TickUntilFunc(func(time.Time) bool { - err := sql.Exec("select 1;") - return err == nil - }, sql.PollContext, sql.PollInterval) -} - // // ========== connection via gorm ========== // // QueryTable returns a gorm.DB to connect to the provided distillery database table -func (sql *SQL) QueryTable(silent bool, table string) (*gorm.DB, error) { +func (sql *SQL) QueryTable(ctx context.Context, silent bool, table string) (*gorm.DB, error) { conn, err := sql.connect(sql.Config.DistilleryDatabase) if err != nil { return nil, err @@ -79,7 +71,7 @@ func (sql *SQL) QueryTable(silent bool, table string) (*gorm.DB, error) { } // set the table - db = db.Table(table) + db = db.WithContext(ctx).Table(table) // check that nothing went wrong if db.Error != nil { @@ -89,12 +81,12 @@ func (sql *SQL) QueryTable(silent bool, table string) (*gorm.DB, error) { } // WaitQueryTable waits for a connection to succeed via QueryTable -func (sql *SQL) WaitQueryTable() error { +func (sql *SQL) WaitQueryTable(ctx context.Context) error { // TODO: Establish a convention on when to wait for this! return timex.TickUntilFunc(func(time.Time) bool { - _, err := sql.QueryTable(true, models.InstanceTable) + _, err := sql.QueryTable(ctx, true, models.InstanceTable) return err == nil - }, sql.PollContext, sql.PollInterval) + }, ctx, sql.PollInterval) } // diff --git a/internal/dis/component/sql/provision.go b/internal/dis/component/sql/provision.go index ba1a43f..d051967 100644 --- a/internal/dis/component/sql/provision.go +++ b/internal/dis/component/sql/provision.go @@ -1,6 +1,7 @@ package sql import ( + "context" "errors" "github.com/FAU-CDI/wisski-distillery/internal/models" @@ -12,15 +13,15 @@ var errProvisionInvalidDatabaseParams = errors.New("Provision: Invalid parameter var errProvisionInvalidGrant = errors.New("Provision: Grant failed") // Provision provisions sql-specific resource for the given instance -func (sql *SQL) Provision(instance models.Instance, domain string) error { - return sql.CreateDatabase(instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword) +func (sql *SQL) Provision(ctx context.Context, instance models.Instance, domain string) error { + return sql.CreateDatabase(ctx, instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword) } // Purge purges sql-specific resources for the given instance -func (sql *SQL) Purge(instance models.Instance, domain string) error { +func (sql *SQL) Purge(ctx context.Context, instance models.Instance, domain string) error { return errorx.First( sql.PurgeDatabase(instance.SqlDatabase), - sql.PurgeUser(instance.SqlUsername), + sql.PurgeUser(ctx, instance.SqlUsername), ) } @@ -28,7 +29,7 @@ func (sql *SQL) Purge(instance models.Instance, domain string) error { // It then generates a new user, with the name 'user' and the password 'password', that is then granted access to this database. // // Provision internally waits for the database to become available. -func (sql *SQL) CreateDatabase(name, user, password string) error { +func (sql *SQL) CreateDatabase(ctx context.Context, name, user, password string) error { // NOTE(twiesing): We shouldn't use string concat to build sql queries. // But the driver doesn't support using query params for this particular query. @@ -43,14 +44,14 @@ func (sql *SQL) CreateDatabase(name, user, password string) error { // Queries of the form "CREATE USER 'test'@'%' IDENTIFIED BY 'test'; FLUSH PRIVILEGES;" return error 1064 when using driver, but are fine with the shell. // This should be fixed eventually, but I have no idea how. - if err := sql.unsafeWaitShell(); err != nil { + if err := sql.unsafeWaitShell(ctx); err != nil { return err } query := "CREATE DATABASE `" + name + "`;" + "CREATE USER '" + user + "'@'%' IDENTIFIED BY '" + password + "';" + "GRANT ALL PRIVILEGES ON `" + name + "`.* TO `" + user + "`@`%`; FLUSH PRIVILEGES;" - if !sql.unsafeQueryShell(query) { + if !sql.unsafeQueryShell(ctx, query) { return errProvisionInvalidGrant } @@ -63,7 +64,7 @@ var errCreateSuperuserGrant = errors.New("CreateSuperUser: Grant failed") // It then grants this user superuser status in the database. // // CreateSuperuser internally waits for the database to become available. -func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error { +func (sql *SQL) CreateSuperuser(ctx context.Context, user, password string, allowExisting bool) error { // NOTE(twiesing): This function unsafely uses the shell directly to create a superuser. // This is for two reasons: // (1) this is used during bootstraping @@ -74,7 +75,7 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error return errProvisionInvalidDatabaseParams } - if err := sql.unsafeWaitShell(); err != nil { + if err := sql.unsafeWaitShell(ctx); err != nil { return err } @@ -85,7 +86,7 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error query := "CREATE USER " + IfNotExists + " '" + user + "'@'%' IDENTIFIED BY '" + password + "';" + "GRANT ALL PRIVILEGES ON *.* TO '" + user + "'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" - if !sql.unsafeQueryShell(query) { + if !sql.unsafeQueryShell(ctx, query) { return errCreateSuperuserGrant } @@ -95,14 +96,14 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error var errPurgeUser = errors.New("PurgeUser: Failed to drop user") // SQLPurgeUser deletes the specified user from the database -func (sql *SQL) PurgeUser(user string) error { +func (sql *SQL) PurgeUser(ctx context.Context, user string) error { if !sqle.IsSafeDatabaseSingleQuote(user) { return errPurgeUser } query := "DROP USER IF EXISTS '" + user + "'@'%';" + "FLUSH PRIVILEGES;" - if !sql.unsafeQueryShell(query) { + if !sql.unsafeQueryShell(ctx, query) { return errPurgeUser } diff --git a/internal/dis/component/sql/snapshot.go b/internal/dis/component/sql/snapshot.go index 195f910..118adc0 100644 --- a/internal/dis/component/sql/snapshot.go +++ b/internal/dis/component/sql/snapshot.go @@ -1,6 +1,7 @@ package sql import ( + "context" "io" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" @@ -12,19 +13,19 @@ func (*SQL) SnapshotNeedsRunning() bool { return false } func (*SQL) SnapshotName() string { return "sql" } -func (sql *SQL) Snapshot(wisski models.Instance, context component.StagingContext) error { - return context.AddDirectory(".", func() error { - return context.AddFile(wisski.SqlDatabase+".sql", func(file io.Writer) error { - return sql.SnapshotDB(context.IO(), file, wisski.SqlDatabase) +func (sql *SQL) Snapshot(wisski models.Instance, scontext component.StagingContext) error { + return scontext.AddDirectory(".", func(ctx context.Context) error { + return scontext.AddFile(wisski.SqlDatabase+".sql", func(ctx context.Context, file io.Writer) error { + return sql.SnapshotDB(ctx, scontext.IO(), file, wisski.SqlDatabase) }) }) } // SnapshotDB makes a backup of the sql database into dest. -func (sql *SQL) SnapshotDB(io stream.IOStream, dest io.Writer, database string) error { +func (sql *SQL) SnapshotDB(ctx context.Context, io stream.IOStream, dest io.Writer, database string) error { io = io.Streams(dest, nil, nil, 0).NonInteractive() - code, err := sql.Stack(sql.Environment).Exec(io, "sql", "mysqldump", "--databases", database) + code, err := sql.Stack(sql.Environment).Exec(ctx, io, "sql", "mysqldump", "--databases", database) if err != nil { return err } diff --git a/internal/dis/component/sql/sql.go b/internal/dis/component/sql/sql.go index 8f4506c..e50e958 100644 --- a/internal/dis/component/sql/sql.go +++ b/internal/dis/component/sql/sql.go @@ -1,7 +1,6 @@ package sql import ( - "context" "embed" "path/filepath" "time" @@ -16,8 +15,7 @@ type SQL struct { ServerURL string // upstream server url - PollContext context.Context // context to abort polling with - PollInterval time.Duration // duration to wait for during wait + PollInterval time.Duration // duration to wait for during wait lazyNetwork lazy.Lazy[string] } diff --git a/internal/dis/component/sql/update.go b/internal/dis/component/sql/update.go index 63fe7c9..56cd059 100644 --- a/internal/dis/component/sql/update.go +++ b/internal/dis/component/sql/update.go @@ -1,6 +1,7 @@ package sql import ( + "context" "errors" "fmt" "time" @@ -16,22 +17,22 @@ import ( // Shell runs a mysql shell with the provided databases. // // NOTE(twiesing): This command should not be used to connect to the database or execute queries except in known situations. -func (sql *SQL) Shell(io stream.IOStream, argv ...string) (int, error) { - return sql.Stack(sql.Environment).Exec(io, "sql", "mysql", argv...) +func (sql *SQL) Shell(ctx context.Context, io stream.IOStream, argv ...string) (int, error) { + return sql.Stack(sql.Environment).Exec(ctx, io, "sql", "mysql", argv...) } // unsafeWaitShell waits for a connection via the database shell to succeed -func (sql *SQL) unsafeWaitShell() error { +func (sql *SQL) unsafeWaitShell(ctx context.Context) error { n := stream.FromNil() return timex.TickUntilFunc(func(time.Time) bool { - code, err := sql.Shell(n, "-e", "select 1;") + code, err := sql.Shell(ctx, n, "-e", "select 1;") return err == nil && code == 0 - }, sql.PollContext, sql.PollInterval) + }, ctx, sql.PollInterval) } // unsafeQuery shell executes a raw database query. -func (sql *SQL) unsafeQueryShell(query string) bool { - code, err := sql.Shell(stream.FromNil(), "-e", query) +func (sql *SQL) unsafeQueryShell(ctx context.Context, query string) bool { + code, err := sql.Shell(ctx, stream.FromNil(), "-e", query) return err == nil && code == 0 } @@ -43,18 +44,18 @@ var errSQLUnableToMigrate = exit.Error{ } // Update initializes or updates the SQL database. -func (sql *SQL) Update(io stream.IOStream) error { +func (sql *SQL) Update(ctx context.Context, io stream.IOStream) error { // unsafely create the admin user! { - if err := sql.unsafeWaitShell(); err != nil { + if err := sql.unsafeWaitShell(ctx); err != nil { return err } logging.LogMessage(io, "Creating administrative user") { username := sql.Config.MysqlAdminUser password := sql.Config.MysqlAdminPassword - if err := sql.CreateSuperuser(username, password, true); err != nil { + if err := sql.CreateSuperuser(ctx, username, password, true); err != nil { return errSQLUnableToCreateUser } } @@ -74,7 +75,7 @@ func (sql *SQL) Update(io stream.IOStream) error { // wait for the database to come up logging.LogMessage(io, "Waiting for database update to be complete") - sql.WaitQueryTable() + sql.WaitQueryTable(ctx) tables := []struct { name string @@ -107,7 +108,7 @@ func (sql *SQL) Update(io stream.IOStream) error { return logging.LogOperation(func() error { for _, table := range tables { logging.LogMessage(io, "migrating %q table", table.name) - db, err := sql.QueryTable(false, table.table) + db, err := sql.QueryTable(ctx, false, table.table) if err != nil { return errSQLUnableToMigrate.WithMessageF(table.name, "unable to access table") } diff --git a/internal/dis/component/ssh2/server_auth.go b/internal/dis/component/ssh2/server_auth.go index afa1cf7..882139a 100644 --- a/internal/dis/component/ssh2/server_auth.go +++ b/internal/dis/component/ssh2/server_auth.go @@ -65,7 +65,7 @@ func (ssh2 *SSH2) handleAuth(ctx ssh.Context, key ssh.PublicKey) bool { // grab permissions for each instance { - instances, err := ssh2.Instances.All() + instances, err := ssh2.Instances.All(ctx) if err != nil { return false } diff --git a/internal/dis/component/stack.go b/internal/dis/component/stack.go index 932051d..19264f5 100644 --- a/internal/dis/component/stack.go +++ b/internal/dis/component/stack.go @@ -4,6 +4,7 @@ package component import ( "bufio" "bytes" + "context" "io/fs" "path/filepath" @@ -33,9 +34,9 @@ var errStackUpdateBuild = errors.New("Stack.Update: Build returned non-zero exit // This does not have a direct 'docker compose' shell equivalent. // // See also Up. -func (ds Stack) Update(io stream.IOStream, start bool) error { +func (ds Stack) Update(ctx context.Context, io stream.IOStream, start bool) error { { - code, err := ds.compose(io, "pull") + code, err := ds.compose(ctx, io, "pull") if err != nil { return err } @@ -45,7 +46,7 @@ func (ds Stack) Update(io stream.IOStream, start bool) error { } { - code, err := ds.compose(io, "build", "--pull") + code, err := ds.compose(ctx, io, "build", "--pull") if err != nil { return err } @@ -54,7 +55,7 @@ func (ds Stack) Update(io stream.IOStream, start bool) error { } } if start { - return ds.Up(io) + return ds.Up(ctx, io) } return nil } @@ -63,8 +64,8 @@ var errStackUp = errors.New("Stack.Up: Up returned non-zero exit code") // Up creates and starts the containers in this Stack. // It is equivalent to 'docker compose up --remove-orphans --detach' on the shell. -func (ds Stack) Up(io stream.IOStream) error { - code, err := ds.compose(io, "up", "--remove-orphans", "--detach") +func (ds Stack) Up(ctx context.Context, io stream.IOStream) error { + code, err := ds.compose(ctx, io, "up", "--remove-orphans", "--detach") if err != nil { return err } @@ -78,7 +79,7 @@ func (ds Stack) Up(io stream.IOStream) error { // It is equivalent to 'docker compose exec $service $executable $args...'. // // It returns the exit code of the process. -func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...string) (int, error) { +func (ds Stack) Exec(ctx context.Context, io stream.IOStream, service, executable string, args ...string) (int, error) { compose := []string{"exec"} if io.StdinIsATerminal() { compose = append(compose, "-ti") @@ -86,14 +87,14 @@ func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...str compose = append(compose, service) compose = append(compose, executable) compose = append(compose, args...) - return ds.compose(io, compose...) + return ds.compose(ctx, io, compose...) } // Run runs a command in a running container with the given executable. // It is equivalent to 'docker compose run [--rm] $service $executable $args...'. // // It returns the exit code of the process. -func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string, args ...string) (int, error) { +func (ds Stack) Run(ctx context.Context, io stream.IOStream, autoRemove bool, service, command string, args ...string) (int, error) { compose := []string{"run"} if autoRemove { compose = append(compose, "--rm") @@ -104,7 +105,7 @@ func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string compose = append(compose, service, command) compose = append(compose, args...) - code, err := ds.compose(io, compose...) + code, err := ds.compose(ctx, io, compose...) if err != nil { return environment.ExecCommandError, nil } @@ -115,8 +116,8 @@ var errStackRestart = errors.New("Stack.Restart: Restart returned non-zero exit // Restart restarts all containers in this Stack. // It is equivalent to 'docker compose restart' on the shell. -func (ds Stack) Restart(io stream.IOStream) error { - code, err := ds.compose(io, "restart") +func (ds Stack) Restart(ctx context.Context, io stream.IOStream) error { + code, err := ds.compose(ctx, io, "restart") if err != nil { return err } @@ -129,12 +130,12 @@ func (ds Stack) Restart(io stream.IOStream) error { var errStackPs = errors.New("Stack.Ps: Down returned non-zero exit code") // Ps returns the ids of the containers currently running -func (ds Stack) Ps(io stream.IOStream) ([]string, error) { +func (ds Stack) Ps(ctx context.Context, io stream.IOStream) ([]string, error) { // create a buffer var buffer bytes.Buffer // read the ids from the command! - code, err := ds.compose(io.Streams(&buffer, nil, nil, 0), "ps", "-q") + code, err := ds.compose(ctx, io.Streams(&buffer, nil, nil, 0), "ps", "-q") if err != nil { return nil, err } @@ -162,8 +163,8 @@ var errStackDown = errors.New("Stack.Down: Down returned non-zero exit code") // Down stops and removes all containers in this Stack. // It is equivalent to 'docker compose down -v' on the shell. -func (ds Stack) Down(io stream.IOStream) error { - code, err := ds.compose(io, "down", "-v") +func (ds Stack) Down(ctx context.Context, io stream.IOStream) error { + code, err := ds.compose(ctx, io, "down", "-v") if err != nil { return err } @@ -177,7 +178,7 @@ func (ds Stack) Down(io stream.IOStream) error { // // NOTE(twiesing): Check if this can be replaced by an internal call to libcompose. // But probably not. -func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) { +func (ds Stack) compose(ctx context.Context, io stream.IOStream, args ...string) (int, error) { if ds.DockerExecutable == "" { var err error ds.DockerExecutable, err = ds.Env.LookPathAbs("docker") @@ -185,7 +186,7 @@ func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) { return environment.ExecCommandError, err } } - return ds.Env.Exec(io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil + return ds.Env.Exec(ctx, io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil } // StackWithResources represents a Stack that can be automatically installed from a set of resources. @@ -218,7 +219,7 @@ type InstallationContext map[string]string // // Installation is non-interactive, but will provide debugging output onto io. // InstallationContext -func (is StackWithResources) Install(io stream.IOStream, context InstallationContext) error { +func (is StackWithResources) Install(ctx context.Context, io stream.IOStream, context InstallationContext) error { env := is.Stack.Env if is.ContextPath != "" { // setup the base files @@ -277,7 +278,7 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon // copy over file from context io.Printf("[copy] %s (from %s)\n", dst, src) - if err := fsx.CopyFile(env, dst, src); err != nil { + if err := fsx.CopyFile(ctx, env, dst, src); err != nil { return errors.Wrapf(err, "Unable to copy file %s", src) } } diff --git a/internal/dis/component/triplestore/backup.go b/internal/dis/component/triplestore/backup.go index de90a32..01096b1 100644 --- a/internal/dis/component/triplestore/backup.go +++ b/internal/dis/component/triplestore/backup.go @@ -1,6 +1,7 @@ package triplestore import ( + "context" "encoding/json" "io" @@ -10,19 +11,17 @@ import ( func (ts *Triplestore) BackupName() string { return "triplestore" } // Backup makes a backup of all Triplestore repositories databases into the path dest. -func (ts *Triplestore) Backup(context component.StagingContext) error { +func (ts *Triplestore) Backup(scontext component.StagingContext) error { + return scontext.AddDirectory("", func(ctx context.Context) error { + // list all the directories + repos, err := ts.listRepositories(ctx) + if err != nil { + return err + } - // list all the directories - repos, err := ts.listRepositories() - if err != nil { - return err - } - - // then backup each file separatly - return context.AddDirectory("", func() error { for _, repo := range repos { - if err := context.AddFile(repo.ID+".nq", func(file io.Writer) error { - _, err := ts.SnapshotDB(file, repo.ID) + if err := scontext.AddFile(repo.ID+".nq", func(ctx context.Context, file io.Writer) error { + _, err := ts.SnapshotDB(ctx, file, repo.ID) return err }); err != nil { return err @@ -32,8 +31,8 @@ func (ts *Triplestore) Backup(context component.StagingContext) error { }) } -func (ts Triplestore) listRepositories() (repos []Repository, err error) { - res, err := ts.OpenRaw("GET", "/rest/repositories", nil, "", "application/json") +func (ts Triplestore) listRepositories(ctx context.Context) (repos []Repository, err error) { + res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "application/json") if err != nil { return nil, err } diff --git a/internal/dis/component/triplestore/database.go b/internal/dis/component/triplestore/database.go index 33b19cd..21f1e0f 100644 --- a/internal/dis/component/triplestore/database.go +++ b/internal/dis/component/triplestore/database.go @@ -2,6 +2,7 @@ package triplestore import ( "bytes" + "context" "encoding/json" "io" "mime/multipart" @@ -30,7 +31,7 @@ type TriplestoreUserAppSettings struct { // // When bodyName is non-empty, expect body to be a byte slice representing a multipart/form-data upload with the given name. // When bodyName is empty, simply marshal body as application/json -func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName string, accept string) (*http.Response, error) { +func (ts Triplestore) OpenRaw(ctx context.Context, method, url string, body interface{}, bodyName string, accept string) (*http.Response, error) { var reader io.Reader var contentType string @@ -66,7 +67,7 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str DisableKeepAlives: true, }, } - req, err := http.NewRequest(method, ts.BaseURL+url, reader) + req, err := http.NewRequestWithContext(ctx, method, ts.BaseURL+url, reader) if err != nil { return nil, err } @@ -86,23 +87,23 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str // Wait waits for the connection to the Triplestore to succeed. // This is achieved using a polling strategy. -func (ts Triplestore) Wait() error { +func (ts Triplestore) Wait(ctx context.Context) error { n := stream.FromNil() return timex.TickUntilFunc(func(time.Time) bool { - res, err := ts.OpenRaw("GET", "/rest/repositories", nil, "", "") + res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "") n.EPrintf("[Triplestore.Wait]: %s\n", err) if err != nil { return false } defer res.Body.Close() return true - }, ts.PollContext, ts.PollInterval) + }, ctx, ts.PollInterval) } // PurgeUser deletes the specified user from the triplestore. // When the user does not exist, returns no error. -func (ts Triplestore) PurgeUser(user string) error { - res, err := ts.OpenRaw("DELETE", "/rest/security/users/"+user, nil, "", "") +func (ts Triplestore) PurgeUser(ctx context.Context, user string) error { + res, err := ts.OpenRaw(ctx, "DELETE", "/rest/security/users/"+user, nil, "", "") if err != nil { return err } @@ -114,8 +115,8 @@ func (ts Triplestore) PurgeUser(user string) error { // PurgeRepo deletes the specified repo from the triplestore. // When the repo does not exist, returns no error. -func (ts Triplestore) PurgeRepo(repo string) error { - res, err := ts.OpenRaw("DELETE", "/rest/repositories/"+repo, nil, "", "") +func (ts Triplestore) PurgeRepo(ctx context.Context, repo string) error { + res, err := ts.OpenRaw(ctx, "DELETE", "/rest/repositories/"+repo, nil, "", "") if err != nil { return err } diff --git a/internal/dis/component/triplestore/provision.go b/internal/dis/component/triplestore/provision.go index 47a5713..9b2c3c4 100644 --- a/internal/dis/component/triplestore/provision.go +++ b/internal/dis/component/triplestore/provision.go @@ -2,6 +2,7 @@ package triplestore import ( "bytes" + "context" "net/http" _ "embed" @@ -20,19 +21,19 @@ var errTripleStoreFailedRepository = exit.Error{ //go:embed create-repo.ttl var createRepoTTL []byte -func (ts *Triplestore) Provision(instance models.Instance, domain string) error { - return ts.CreateRepository(instance.GraphDBRepository, domain, instance.GraphDBUsername, instance.GraphDBPassword) +func (ts *Triplestore) Provision(ctx context.Context, instance models.Instance, domain string) error { + return ts.CreateRepository(ctx, instance.GraphDBRepository, domain, instance.GraphDBUsername, instance.GraphDBPassword) } -func (ts *Triplestore) Purge(instance models.Instance, domain string) error { +func (ts *Triplestore) Purge(ctx context.Context, instance models.Instance, domain string) error { return errorx.First( - ts.PurgeRepo(instance.GraphDBRepository), - ts.PurgeUser(instance.GraphDBUsername), + ts.PurgeRepo(ctx, instance.GraphDBRepository), + ts.PurgeUser(ctx, instance.GraphDBUsername), ) } -func (ts *Triplestore) CreateRepository(name, domain, user, password string) error { - if err := ts.Wait(); err != nil { +func (ts *Triplestore) CreateRepository(ctx context.Context, name, domain, user, password string) error { + if err := ts.Wait(ctx); err != nil { return err } @@ -48,7 +49,7 @@ func (ts *Triplestore) CreateRepository(name, domain, user, password string) err // do the create! { - res, err := ts.OpenRaw("POST", "/rest/repositories", createRepo.Bytes(), "config", "") + res, err := ts.OpenRaw(ctx, "POST", "/rest/repositories", createRepo.Bytes(), "config", "") if err != nil { return errTripleStoreFailedRepository.WithMessageF(err) } @@ -60,7 +61,7 @@ func (ts *Triplestore) CreateRepository(name, domain, user, password string) err // create the user and grant them access { - res, err := ts.OpenRaw("POST", "/rest/security/users/"+user, TriplestoreUserPayload{ + res, err := ts.OpenRaw(ctx, "POST", "/rest/security/users/"+user, TriplestoreUserPayload{ Password: password, AppSettings: TriplestoreUserAppSettings{ DefaultInference: true, diff --git a/internal/dis/component/triplestore/snapshot.go b/internal/dis/component/triplestore/snapshot.go index c2d395b..f2c90d4 100644 --- a/internal/dis/component/triplestore/snapshot.go +++ b/internal/dis/component/triplestore/snapshot.go @@ -1,6 +1,7 @@ package triplestore import ( + "context" "io" "net/http" @@ -13,10 +14,10 @@ func (Triplestore) SnapshotNeedsRunning() bool { return false } func (Triplestore) SnapshotName() string { return "triplestore" } -func (ts *Triplestore) Snapshot(wisski models.Instance, context component.StagingContext) error { - return context.AddDirectory(".", func() error { - return context.AddFile(wisski.GraphDBRepository+".nq", func(file io.Writer) error { - _, err := ts.SnapshotDB(file, wisski.GraphDBRepository) +func (ts *Triplestore) Snapshot(wisski models.Instance, scontext component.StagingContext) error { + return scontext.AddDirectory(".", func(ctx context.Context) error { + return scontext.AddFile(wisski.GraphDBRepository+".nq", func(ctx context.Context, file io.Writer) error { + _, err := ts.SnapshotDB(ctx, file, wisski.GraphDBRepository) return err }) }) @@ -25,8 +26,8 @@ func (ts *Triplestore) Snapshot(wisski models.Instance, context component.Stagin var errTSBackupWrongStatusCode = errors.New("Triplestore.Backup: Wrong status code") // SnapshotDB snapshots the provided repository into dst -func (ts Triplestore) SnapshotDB(dst io.Writer, repo string) (int64, error) { - res, err := ts.OpenRaw("GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads") +func (ts Triplestore) SnapshotDB(ctx context.Context, dst io.Writer, repo string) (int64, error) { + res, err := ts.OpenRaw(ctx, "GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads") if err != nil { return 0, err } diff --git a/internal/dis/component/triplestore/triplestore.go b/internal/dis/component/triplestore/triplestore.go index fd75dc2..8631a4f 100644 --- a/internal/dis/component/triplestore/triplestore.go +++ b/internal/dis/component/triplestore/triplestore.go @@ -1,7 +1,6 @@ package triplestore import ( - "context" "embed" "path/filepath" "time" @@ -15,8 +14,7 @@ type Triplestore struct { BaseURL string // upstream server url - PollContext context.Context // context to abort polling with - PollInterval time.Duration // duration to wait for during wait + PollInterval time.Duration // duration to wait for during wait } func (ts *Triplestore) Path() string { diff --git a/internal/dis/component/triplestore/update.go b/internal/dis/component/triplestore/update.go index d6aefb9..eff3c4e 100644 --- a/internal/dis/component/triplestore/update.go +++ b/internal/dis/component/triplestore/update.go @@ -1,6 +1,7 @@ package triplestore import ( + "context" "fmt" "net/http" @@ -11,15 +12,15 @@ import ( var errTriplestoreFailedSecurity = errors.New("failed to enable triplestore security: request did not succeed with HTTP 200 OK") -func (ts Triplestore) Update(io stream.IOStream) error { +func (ts Triplestore) Update(ctx context.Context, io stream.IOStream) error { logging.LogMessage(io, "Waiting for Triplestore") - if err := ts.Wait(); err != nil { + if err := ts.Wait(ctx); err != nil { return err } logging.LogMessage(io, "Resetting admin user password") { - res, err := ts.OpenRaw("PUT", "/rest/security/users/"+ts.Config.TriplestoreAdminUser, TriplestoreUserPayload{ + res, err := ts.OpenRaw(ctx, "PUT", "/rest/security/users/"+ts.Config.TriplestoreAdminUser, TriplestoreUserPayload{ Password: ts.Config.TriplestoreAdminPassword, AppSettings: TriplestoreUserAppSettings{ DefaultInference: true, @@ -51,7 +52,7 @@ func (ts Triplestore) Update(io stream.IOStream) error { logging.LogMessage(io, "Enabling Triplestore security") { - res, err := ts.OpenRaw("POST", "/rest/security", true, "", "") + res, err := ts.OpenRaw(ctx, "POST", "/rest/security", true, "", "") if err != nil { return fmt.Errorf("failed to enable triplestore security: %s", err) } diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index 9f13cc2..6ce7893 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -2,7 +2,7 @@ package dis import ( - "context" + "io" "sync" "time" @@ -32,8 +32,8 @@ type Distillery struct { // core holds the core of the distillery component.Still - // internal context for the distillery - context context.Context + // Where interactive progress is displayed + Progress io.Writer // Upstream holds information to connect to the various running // distillery components. @@ -54,11 +54,6 @@ type Upstream struct { Solr string } -// Context returns a new Context belonging to this distillery -func (dis *Distillery) Context() context.Context { - return dis.context -} - // // PUBLIC COMPONENT GETTERS // @@ -110,17 +105,14 @@ func (dis *Distillery) allComponents() []initFunc { manual(func(ts *triplestore.Triplestore) { ts.BaseURL = "http://" + dis.Upstream.Triplestore - ts.PollContext = dis.Context() ts.PollInterval = time.Second }), manual(func(sql *sql.SQL) { sql.ServerURL = dis.Upstream.SQL - sql.PollContext = dis.Context() sql.PollInterval = time.Second }), manual(func(s *solr.Solr) { s.BaseURL = dis.Upstream.Solr - s.PollContext = dis.Context() s.PollInterval = time.Second }), diff --git a/internal/dis/init.go b/internal/dis/init.go index 6049d10..e8776e2 100644 --- a/internal/dis/init.go +++ b/internal/dis/init.go @@ -21,7 +21,6 @@ var errOpenConfig = exit.Error{ // NewDistillery creates a new distillery from the provided flags func NewDistillery(params cli.Params, flags cli.Flags, req cli.Requirements) (dis *Distillery, err error) { dis = &Distillery{ - context: params.Context, Still: component.Still{ Environment: new(environment.Native), }, diff --git a/internal/phpx/php.go b/internal/phpx/php.go index a830fee..830be92 100644 --- a/internal/phpx/php.go +++ b/internal/phpx/php.go @@ -1,18 +1,22 @@ // Package phpx provides functionalities for interacting with PHP code package phpx -import "github.com/tkw1536/goprogram/stream" +import ( + "context" + + "github.com/tkw1536/goprogram/stream" +) // Executor represents anything that can spawn type Executor interface { // Spawn spawns a new (independent) process executing code. // It should return only once the execution terminates. - Spawn(str stream.IOStream, code string) error + Spawn(ctx context.Context, str stream.IOStream, code string) error } // SpawnFunc implements Executor -type SpawnFunc func(str stream.IOStream, code string) error +type SpawnFunc func(ctx context.Context, str stream.IOStream, code string) error -func (sf SpawnFunc) Spawn(str stream.IOStream, code string) error { - return sf(str, code) +func (sf SpawnFunc) Spawn(ctx context.Context, str stream.IOStream, code string) error { + return sf(ctx, str, code) } diff --git a/internal/phpx/server.go b/internal/phpx/server.go index d0ac88f..f7bebb7 100644 --- a/internal/phpx/server.go +++ b/internal/phpx/server.go @@ -10,6 +10,7 @@ import ( _ "embed" + "github.com/FAU-CDI/wisski-distillery/pkg/cancel" "github.com/FAU-CDI/wisski-distillery/pkg/lazy" "github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/lib/nobufio" @@ -21,6 +22,9 @@ import ( // // A server, once used, should be closed using the [Close] method. type Server struct { + // Context to use for the server + Context context.Context + // Executor is the executor used by this server. // It may not be modified concurrently with other processes. Executor Executor @@ -35,8 +39,8 @@ type Server struct { m sync.Mutex // prevents concurrent access on any of the methods - c context.Context // closed when server is finished - + cancel context.CancelFunc + c context.Context // closed when server is finished } func (server *Server) prepare() error { @@ -57,7 +61,8 @@ func (server *Server) prepare() error { } // create a context to close the server - context, cancel := context.WithCancel(context.Background()) + context, cancel := context.WithCancel(server.Context) + server.cancel = cancel // start the shell process, which will close everything once done go func() { @@ -67,12 +72,12 @@ func (server *Server) prepare() error { or.Close() ow.Close() - cancel() + server.cancel() }() // start the server io := stream.NewIOStream(ow, nil, ir, 0) - err := server.Executor.Spawn(io, serverPHP) + err := server.Executor.Spawn(server.c, io, serverPHP) server.err.Set(ServerError{errClosed, err}) }() @@ -91,7 +96,7 @@ func (server *Server) prepare() error { // as such any functions defined will remain in server memory. // // When an exception is thrown by the PHP Code, error is not nil, and dest remains unchanged. -func (server *Server) MarshalEval(value any, code string) error { +func (server *Server) MarshalEval(ctx context.Context, value any, code string) error { if err := server.prepare(); err != nil { return err } @@ -111,8 +116,11 @@ func (server *Server) MarshalEval(value any, code string) error { // find a delimiter for the code, and then send io.WriteString(server.in, input) - // read the next line (as a response) - data, err := nobufio.ReadLine(server.out) + data, err, _ := cancel.WithContext2(ctx, func(start func()) (string, error) { + return nobufio.ReadLine(server.out) + }, func() { + server.cancel() + }) if err != nil { return ServerError{Message: errReceive, Err: err} } @@ -139,8 +147,8 @@ func (server *Server) MarshalEval(value any, code string) error { } // Eval is like [MarshalEval], but returns the value as an any -func (server *Server) Eval(code string) (value any, err error) { - err = server.MarshalEval(&value, code) +func (server *Server) Eval(ctx context.Context, code string) (value any, err error) { + err = server.MarshalEval(ctx, &value, code) return } @@ -148,7 +156,7 @@ func (server *Server) Eval(code string) (value any, err error) { // Arguments are sent to php using json Marshal, and are 'json_decode'd on the php side. // // Return values are received as in [MarshalEval]. -func (server *Server) MarshalCall(value any, function string, args ...any) error { +func (server *Server) MarshalCall(ctx context.Context, value any, function string, args ...any) error { // name of function to call name := MarshalString(function) @@ -172,12 +180,12 @@ func (server *Server) MarshalCall(value any, function string, args ...any) error } // and evaluate the code - return server.MarshalEval(value, code) + return server.MarshalEval(ctx, value, code) } // Call is like [MarshalCall] but returns the return value of the function as an any -func (server *Server) Call(function string, args ...any) (value any, err error) { - err = server.MarshalCall(&value, function, args...) +func (server *Server) Call(ctx context.Context, function string, args ...any) (value any, err error) { + err = server.MarshalCall(ctx, &value, function, args...) return } diff --git a/internal/wisski/ingredient/barrel/build.go b/internal/wisski/ingredient/barrel/build.go index e9d1668..1975221 100644 --- a/internal/wisski/ingredient/barrel/build.go +++ b/internal/wisski/ingredient/barrel/build.go @@ -1,6 +1,7 @@ package barrel import ( + "context" "time" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" @@ -15,40 +16,40 @@ import ( // Build builds or rebuilds the barel connected to this instance. // // It also logs the current time into the metadata belonging to this instance. -func (barrel *Barrel) Build(stream stream.IOStream, start bool) error { - if !barrel.Locker.TryLock() { +func (barrel *Barrel) Build(ctx context.Context, stream stream.IOStream, start bool) error { + if !barrel.Locker.TryLock(ctx) { err := locker.Locked return err } - defer barrel.Locker.Unlock() + defer barrel.Locker.Unlock(ctx) stack := barrel.Stack() var context component.InstallationContext { - err := stack.Install(stream, context) + err := stack.Install(ctx, stream, context) if err != nil { return err } } { - err := stack.Update(stream, start) + err := stack.Update(ctx, stream, start) if err != nil { return err } } // store the current last rebuild - return barrel.setLastRebuild() + return barrel.setLastRebuild(ctx) } // TODO: Move this to time.Time var lastRebuild = mstore.For[int64]("lastRebuild") -func (barrel Barrel) LastRebuild() (t time.Time, err error) { - epoch, err := lastRebuild.Get(barrel.MStore) +func (barrel Barrel) LastRebuild(ctx context.Context) (t time.Time, err error) { + epoch, err := lastRebuild.Get(ctx, barrel.MStore) if err == meta.ErrMetadatumNotSet { return t, nil } @@ -60,8 +61,8 @@ func (barrel Barrel) LastRebuild() (t time.Time, err error) { return time.Unix(epoch, 0), nil } -func (barrel *Barrel) setLastRebuild() error { - return lastRebuild.Set(barrel.MStore, time.Now().Unix()) +func (barrel *Barrel) setLastRebuild(ctx context.Context) error { + return lastRebuild.Set(ctx, barrel.MStore, time.Now().Unix()) } type LastRebuildFetcher struct { @@ -70,7 +71,7 @@ type LastRebuildFetcher struct { Barrel *Barrel } -func (lbr *LastRebuildFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { - info.LastRebuild, _ = lbr.Barrel.LastRebuild() +func (lbr *LastRebuildFetcher) Fetch(ctx context.Context, flags ingredient.FetcherFlags, info *status.WissKI) (err error) { + info.LastRebuild, _ = lbr.Barrel.LastRebuild(ctx) return } diff --git a/internal/wisski/ingredient/barrel/drush/cron.go b/internal/wisski/ingredient/barrel/drush/cron.go index 0aa816a..4a5c572 100644 --- a/internal/wisski/ingredient/barrel/drush/cron.go +++ b/internal/wisski/ingredient/barrel/drush/cron.go @@ -1,6 +1,7 @@ package drush import ( + "context" "time" "github.com/FAU-CDI/wisski-distillery/internal/phpx" @@ -15,8 +16,8 @@ var errCronFailed = exit.Error{ ExitCode: exit.ExitGeneric, } -func (drush *Drush) Cron(io stream.IOStream) error { - code, err := drush.Barrel.Shell(io, "/runtime/cron.sh") +func (drush *Drush) Cron(ctx context.Context, io stream.IOStream) error { + code, err := drush.Barrel.Shell(ctx, io, "/runtime/cron.sh") if err != nil { io.EPrintln(err) } @@ -29,9 +30,9 @@ func (drush *Drush) Cron(io stream.IOStream) error { return nil } -func (drush *Drush) LastCron(server *phpx.Server) (t time.Time, err error) { +func (drush *Drush) LastCron(ctx context.Context, server *phpx.Server) (t time.Time, err error) { var timestamp int64 - err = drush.PHP.EvalCode(server, ×tamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `) + err = drush.PHP.EvalCode(ctx, server, ×tamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `) if err != nil { return } @@ -49,6 +50,6 @@ func (lbr *LastCronFetcher) Fetch(flags ingredient.FetcherFlags, info *status.Wi return } - info.LastRebuild, _ = lbr.Drush.LastCron(flags.Server) + info.LastRebuild, _ = lbr.Drush.LastCron(flags.Context, flags.Server) return } diff --git a/internal/wisski/ingredient/barrel/drush/update.go b/internal/wisski/ingredient/barrel/drush/update.go index bd5aea0..7766c64 100644 --- a/internal/wisski/ingredient/barrel/drush/update.go +++ b/internal/wisski/ingredient/barrel/drush/update.go @@ -1,6 +1,7 @@ package drush import ( + "context" "time" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" @@ -18,8 +19,8 @@ var errBlindUpdateFailed = exit.Error{ } // Update performs a blind drush update -func (drush *Drush) Update(io stream.IOStream) error { - code, err := drush.Barrel.Shell(io, "/runtime/blind_update.sh") +func (drush *Drush) Update(ctx context.Context, io stream.IOStream) error { + code, err := drush.Barrel.Shell(ctx, io, "/runtime/blind_update.sh") if err != nil { return errBlindUpdateFailed.WithMessageF(drush.Slug, environment.ExecCommandError) } @@ -27,13 +28,13 @@ func (drush *Drush) Update(io stream.IOStream) error { return errBlindUpdateFailed.WithMessageF(drush.Slug, code) } - return drush.setLastUpdate() + return drush.setLastUpdate(ctx) } const lastUpdate = mstore.For[int64]("lastUpdate") -func (drush *Drush) LastUpdate() (t time.Time, err error) { - epoch, err := lastUpdate.Get(drush.MStore) +func (drush *Drush) LastUpdate(ctx context.Context) (t time.Time, err error) { + epoch, err := lastUpdate.Get(ctx, drush.MStore) if err == meta.ErrMetadatumNotSet { return t, nil } @@ -45,8 +46,8 @@ func (drush *Drush) LastUpdate() (t time.Time, err error) { return time.Unix(epoch, 0), nil } -func (drush *Drush) setLastUpdate() error { - return lastUpdate.Set(drush.MStore, time.Now().Unix()) +func (drush *Drush) setLastUpdate(ctx context.Context) error { + return lastUpdate.Set(ctx, drush.MStore, time.Now().Unix()) } type LastUpdateFetcher struct { @@ -56,6 +57,6 @@ type LastUpdateFetcher struct { } func (lbr *LastUpdateFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { - info.LastUpdate, err = lbr.Drush.LastUpdate() + info.LastUpdate, err = lbr.Drush.LastUpdate(flags.Context) return } diff --git a/internal/wisski/ingredient/barrel/provisioner/provisioner.go b/internal/wisski/ingredient/barrel/provisioner/provisioner.go index ed03c55..4b64dcc 100644 --- a/internal/wisski/ingredient/barrel/provisioner/provisioner.go +++ b/internal/wisski/ingredient/barrel/provisioner/provisioner.go @@ -1,6 +1,7 @@ package provisioner import ( + "context" "errors" "strings" @@ -19,10 +20,10 @@ type Provisioner struct { } // Provision provisions an instance, assuming that the required databases already exist. -func (provision *Provisioner) Provision(io stream.IOStream) error { +func (provision *Provisioner) Provision(ctx context.Context, io stream.IOStream) error { // build the container - if err := provision.Barrel.Build(io, false); err != nil { + if err := provision.Barrel.Build(ctx, io, false); err != nil { return err } @@ -53,7 +54,7 @@ func (provision *Provisioner) Provision(io stream.IOStream) error { // TODO: Move the provision script into the control plane! provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ") - code, err := provision.Barrel.Stack().Run(io, true, "barrel", "/bin/bash", "-c", provisionScript) + code, err := provision.Barrel.Stack().Run(ctx, io, true, "barrel", "/bin/bash", "-c", provisionScript) if err != nil { return err } diff --git a/internal/wisski/ingredient/barrel/running.go b/internal/wisski/ingredient/barrel/running.go index 95b0de9..2454ab9 100644 --- a/internal/wisski/ingredient/barrel/running.go +++ b/internal/wisski/ingredient/barrel/running.go @@ -1,14 +1,16 @@ package barrel import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/tkw1536/goprogram/stream" ) // Running checks if this WissKI is currently running. -func (barrel *Barrel) Running() (bool, error) { - ps, err := barrel.Stack().Ps(stream.FromNil()) +func (barrel *Barrel) Running(ctx context.Context) (bool, error) { + ps, err := barrel.Stack().Ps(ctx, stream.FromNil()) if err != nil { return false, err } @@ -22,6 +24,6 @@ type RunningFetcher struct { } func (rf *RunningFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { - info.Running, err = rf.Barrel.Running() + info.Running, err = rf.Barrel.Running(flags.Context) return } diff --git a/internal/wisski/ingredient/barrel/shell.go b/internal/wisski/ingredient/barrel/shell.go index 5cb8365..5e87514 100644 --- a/internal/wisski/ingredient/barrel/shell.go +++ b/internal/wisski/ingredient/barrel/shell.go @@ -1,8 +1,12 @@ package barrel -import "github.com/tkw1536/goprogram/stream" +import ( + "context" + + "github.com/tkw1536/goprogram/stream" +) // Shell executes a shell command inside the instance. -func (barrel *Barrel) Shell(io stream.IOStream, argv ...string) (int, error) { - return barrel.Stack().Exec(io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...) +func (barrel *Barrel) Shell(ctx context.Context, io stream.IOStream, argv ...string) (int, error) { + return barrel.Stack().Exec(ctx, io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...) } diff --git a/internal/wisski/ingredient/bookkeeping/bookkeeping.go b/internal/wisski/ingredient/bookkeeping/bookkeeping.go index 51ed685..2cb30b3 100644 --- a/internal/wisski/ingredient/bookkeeping/bookkeeping.go +++ b/internal/wisski/ingredient/bookkeeping/bookkeeping.go @@ -1,6 +1,8 @@ package bookkeeping import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" ) @@ -11,8 +13,8 @@ type Bookkeeping struct { } // Save saves this instance in the bookkeeping table -func (bk *Bookkeeping) Save() error { - sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable) +func (bk *Bookkeeping) Save(ctx context.Context) error { + sdb, err := bk.Malt.SQL.QueryTable(ctx, false, models.InstanceTable) if err != nil { return err } @@ -27,8 +29,8 @@ func (bk *Bookkeeping) Save() error { } // Delete deletes this instance from the bookkeeping table -func (bk *Bookkeeping) Delete() error { - sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable) +func (bk *Bookkeeping) Delete(ctx context.Context) error { + sdb, err := bk.Malt.SQL.QueryTable(ctx, false, models.InstanceTable) if err != nil { return err } diff --git a/internal/wisski/ingredient/fetcher.go b/internal/wisski/ingredient/fetcher.go index 92e9c37..5ea09db 100644 --- a/internal/wisski/ingredient/fetcher.go +++ b/internal/wisski/ingredient/fetcher.go @@ -1,6 +1,8 @@ package ingredient import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/status" ) @@ -15,6 +17,7 @@ type WissKIFetcher interface { // FetcherFlags describes options for a WissKIFetcher type FetcherFlags struct { - Quick bool - Server *phpx.Server + Context context.Context + Quick bool + Server *phpx.Server } diff --git a/internal/wisski/ingredient/info/info.go b/internal/wisski/ingredient/info/info.go index 3774766..5383ce1 100644 --- a/internal/wisski/ingredient/info/info.go +++ b/internal/wisski/ingredient/info/info.go @@ -1,6 +1,7 @@ package info import ( + "context" "time" "github.com/FAU-CDI/wisski-distillery/internal/status" @@ -21,10 +22,11 @@ type Info struct { // Information fetches information about this WissKI. // TODO: Rework this to be able to determine what kind of information is available. -func (wisski *Info) Information(quick bool) (info status.WissKI, err error) { +func (wisski *Info) Information(ctx context.Context, quick bool) (info status.WissKI, err error) { // setup flags flags := ingredient.FetcherFlags{ - Quick: quick, + Quick: quick, + Context: ctx, } // potentially setup a new server diff --git a/internal/wisski/ingredient/info/snapshots.go b/internal/wisski/ingredient/info/snapshots.go index 91d8506..5b1806d 100644 --- a/internal/wisski/ingredient/info/snapshots.go +++ b/internal/wisski/ingredient/info/snapshots.go @@ -16,6 +16,6 @@ func (lbr *SnapshotsFetcher) Fetch(flags ingredient.FetcherFlags, info *status.W return } - info.Snapshots, _ = lbr.Snapshots() + info.Snapshots, _ = lbr.Snapshots(flags.Context) return } diff --git a/internal/wisski/ingredient/locker/lock.go b/internal/wisski/ingredient/locker/lock.go index 94014d1..46ca1b3 100644 --- a/internal/wisski/ingredient/locker/lock.go +++ b/internal/wisski/ingredient/locker/lock.go @@ -1,6 +1,8 @@ package locker import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/tkw1536/goprogram/exit" @@ -17,8 +19,8 @@ var Locked = exit.Error{ } // TryLock attemps to lock this WissKI and returns if it suceeded -func (lock *Locker) TryLock() bool { - table, err := lock.Malt.SQL.QueryTable(true, models.LockTable) +func (lock *Locker) TryLock(ctx context.Context) bool { + table, err := lock.Malt.SQL.QueryTable(ctx, true, models.LockTable) if err != nil { return false } @@ -29,8 +31,8 @@ func (lock *Locker) TryLock() bool { // TryUnlock attempts to unlock this WissKI and reports if it succeeded. // An unlock can only -func (lock *Locker) TryUnlock() bool { - table, err := lock.Malt.SQL.QueryTable(true, models.LockTable) +func (lock *Locker) TryUnlock(ctx context.Context) bool { + table, err := lock.Malt.SQL.QueryTable(ctx, true, models.LockTable) if err != nil { return false } @@ -39,6 +41,6 @@ func (lock *Locker) TryUnlock() bool { } // Unlock unlocks this WissKI, ignoring any error. -func (lock *Locker) Unlock() { - lock.TryUnlock() +func (lock *Locker) Unlock(ctx context.Context) { + lock.TryUnlock(ctx) } diff --git a/internal/wisski/ingredient/locker/locked.go b/internal/wisski/ingredient/locker/locked.go index c485f26..5718f76 100644 --- a/internal/wisski/ingredient/locker/locked.go +++ b/internal/wisski/ingredient/locker/locked.go @@ -1,14 +1,17 @@ package locker import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" ) // Locked checks if this WissKI is currently locked. -func (lock *Locker) Locked() (locked bool) { - table, err := lock.SQL.QueryTable(true, models.LockTable) +// If an error occurs, the instance is considered not locked. +func (lock *Locker) Locked(ctx context.Context) (locked bool) { + table, err := lock.SQL.QueryTable(ctx, true, models.LockTable) if err != nil { return false } @@ -19,6 +22,6 @@ func (lock *Locker) Locked() (locked bool) { } func (locker *Locker) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { - info.Locked = locker.Locked() + info.Locked = locker.Locked(flags.Context) return } diff --git a/internal/wisski/ingredient/mstore/mstore.go b/internal/wisski/ingredient/mstore/mstore.go index 7f75cb8..b95009f 100644 --- a/internal/wisski/ingredient/mstore/mstore.go +++ b/internal/wisski/ingredient/mstore/mstore.go @@ -1,6 +1,8 @@ package mstore import ( + "context" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" ) @@ -14,26 +16,26 @@ type MStore struct { // For is a Store for the provided value type For[Value any] meta.TypedKey[Value] -func (f For[Value]) Get(m *MStore) (value Value, err error) { - return meta.TypedKey[Value](f).Get(m.Storage) +func (f For[Value]) Get(ctx context.Context, m *MStore) (value Value, err error) { + return meta.TypedKey[Value](f).Get(ctx, m.Storage) } -func (f For[Value]) GetAll(m *MStore) (values []Value, err error) { - return meta.TypedKey[Value](f).GetAll(m.Storage) +func (f For[Value]) GetAll(ctx context.Context, m *MStore) (values []Value, err error) { + return meta.TypedKey[Value](f).GetAll(ctx, m.Storage) } -func (f For[Value]) GetOrSet(m *MStore, dflt Value) (value Value, err error) { - return meta.TypedKey[Value](f).GetOrSet(m.Storage, dflt) +func (f For[Value]) GetOrSet(ctx context.Context, m *MStore, dflt Value) (value Value, err error) { + return meta.TypedKey[Value](f).GetOrSet(ctx, m.Storage, dflt) } -func (f For[Value]) Set(m *MStore, value Value) error { - return meta.TypedKey[Value](f).Set(m.Storage, value) +func (f For[Value]) Set(ctx context.Context, m *MStore, value Value) error { + return meta.TypedKey[Value](f).Set(ctx, m.Storage, value) } -func (f For[Value]) SetAll(m *MStore, values ...Value) error { - return meta.TypedKey[Value](f).SetAll(m.Storage, values...) +func (f For[Value]) SetAll(ctx context.Context, m *MStore, values ...Value) error { + return meta.TypedKey[Value](f).SetAll(ctx, m.Storage, values...) } -func (f For[Value]) Delete(m *MStore) error { - return m.Storage.Delete(meta.Key(f)) +func (f For[Value]) Delete(ctx context.Context, m *MStore) error { + return m.Storage.Delete(ctx, meta.Key(f)) } diff --git a/internal/wisski/ingredient/php/extras/pathbuilder.go b/internal/wisski/ingredient/php/extras/pathbuilder.go index e160ade..823d444 100644 --- a/internal/wisski/ingredient/php/extras/pathbuilder.go +++ b/internal/wisski/ingredient/php/extras/pathbuilder.go @@ -1,6 +1,7 @@ package extras import ( + "context" _ "embed" "github.com/FAU-CDI/wisski-distillery/internal/phpx" @@ -22,8 +23,8 @@ var pathbuilderPHP string // All returns the ids of all pathbuilders in consistent order. // // server is the server to fetch the pathbuilders from, any may be nil. -func (pathbuilder *Pathbuilder) All(server *phpx.Server) (ids []string, err error) { - err = pathbuilder.PHP.ExecScript(server, &ids, pathbuilderPHP, "all_list") +func (pathbuilder *Pathbuilder) All(ctx context.Context, server *phpx.Server) (ids []string, err error) { + err = pathbuilder.PHP.ExecScript(ctx, server, &ids, pathbuilderPHP, "all_list") slices.Sort(ids) return } @@ -32,16 +33,16 @@ func (pathbuilder *Pathbuilder) All(server *phpx.Server) (ids []string, err erro // If it does not exist, it returns the empty string and nil error. // // server is the server to fetch the pathbuilders from, any may be nil. -func (pathbuilder *Pathbuilder) Get(server *phpx.Server, id string) (xml string, err error) { - err = pathbuilder.PHP.ExecScript(server, &xml, pathbuilderPHP, "one_xml", id) +func (pathbuilder *Pathbuilder) Get(ctx context.Context, server *phpx.Server, id string) (xml string, err error) { + err = pathbuilder.PHP.ExecScript(ctx, server, &xml, pathbuilderPHP, "one_xml", id) return } // GetAll returns all pathbuilders serialized as xml // // server is the server to fetch the pathbuilders from, any may be nil. -func (pathbuilder *Pathbuilder) GetAll(server *phpx.Server) (pathbuilders map[string]string, err error) { - err = pathbuilder.PHP.ExecScript(server, &pathbuilders, pathbuilderPHP, "all_xml") +func (pathbuilder *Pathbuilder) GetAll(ctx context.Context, server *phpx.Server) (pathbuilders map[string]string, err error) { + err = pathbuilder.PHP.ExecScript(ctx, server, &pathbuilders, pathbuilderPHP, "all_xml") return } @@ -50,6 +51,6 @@ func (pathbuilder *Pathbuilder) Fetch(flags ingredient.FetcherFlags, info *statu return } - info.Pathbuilders, _ = pathbuilder.GetAll(flags.Server) + info.Pathbuilders, _ = pathbuilder.GetAll(flags.Context, flags.Server) return } diff --git a/internal/wisski/ingredient/php/extras/prefixes.go b/internal/wisski/ingredient/php/extras/prefixes.go index 0c0db5c..4e7c4ef 100644 --- a/internal/wisski/ingredient/php/extras/prefixes.go +++ b/internal/wisski/ingredient/php/extras/prefixes.go @@ -2,6 +2,7 @@ package extras import ( "bufio" + "context" "path/filepath" "strings" @@ -37,8 +38,8 @@ var listURIPrefixesPHP string // // server is an optional server to fetch prefixes from. // server may be nil. -func (prefixes *Prefixes) All(server *phpx.Server) ([]string, error) { - uris, err := prefixes.database(server) +func (prefixes *Prefixes) All(ctx context.Context, server *phpx.Server) ([]string, error) { + uris, err := prefixes.database(ctx, server) if err != nil { return nil, err } @@ -51,9 +52,9 @@ func (prefixes *Prefixes) All(server *phpx.Server) ([]string, error) { return append(uris, uris2...), nil } -func (wisski *Prefixes) database(server *phpx.Server) (prefixes []string, err error) { +func (wisski *Prefixes) database(ctx context.Context, server *phpx.Server) (prefixes []string, err error) { // get all the ugly prefixes - err = wisski.PHP.ExecScript(server, &prefixes, listURIPrefixesPHP, "list_prefixes") + err = wisski.PHP.ExecScript(ctx, server, &prefixes, listURIPrefixesPHP, "list_prefixes") if err != nil { return nil, err } @@ -143,28 +144,28 @@ func (wisski *Prefixes) filePrefixes() (prefixes []string, err error) { var prefix = mstore.For[string]("prefix") // Prefixes returns the cached prefixes from the given instance -func (wisski *Prefixes) AllCached() (results []string, err error) { - return prefix.GetAll(wisski.MStore) +func (wisski *Prefixes) AllCached(ctx context.Context) (results []string, err error) { + return prefix.GetAll(ctx, wisski.MStore) } // Update updates the cached prefixes of this instance -func (wisski *Prefixes) Update() error { - prefixes, err := wisski.All(nil) +func (wisski *Prefixes) Update(ctx context.Context) error { + prefixes, err := wisski.All(ctx, nil) if err != nil { return err } - return prefix.SetAll(wisski.MStore, prefixes...) + return prefix.SetAll(ctx, wisski.MStore, prefixes...) } func (prefixes *Prefixes) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { info.NoPrefixes = prefixes.NoPrefix() if flags.Quick { // quick mode: grab only the cached prefixes - info.Prefixes, _ = prefixes.AllCached() + info.Prefixes, _ = prefixes.AllCached(flags.Context) } else { // slow mode: grab the fresh prefixes from the server // TODO: Do we want to update them while we are at it? - info.Prefixes, _ = prefixes.All(flags.Server) + info.Prefixes, _ = prefixes.All(flags.Context, flags.Server) } return } diff --git a/internal/wisski/ingredient/php/extras/settings.go b/internal/wisski/ingredient/php/extras/settings.go index 168d709..6c1267f 100644 --- a/internal/wisski/ingredient/php/extras/settings.go +++ b/internal/wisski/ingredient/php/extras/settings.go @@ -1,6 +1,7 @@ package extras import ( + "context" _ "embed" "github.com/FAU-CDI/wisski-distillery/internal/phpx" @@ -17,11 +18,11 @@ type Settings struct { //go:embed settings.php var settingsPHP string -func (settings *Settings) Get(server *phpx.Server, key string) (value any, err error) { - err = settings.PHP.ExecScript(server, &value, settingsPHP, "get_setting", key) +func (settings *Settings) Get(ctx context.Context, server *phpx.Server, key string) (value any, err error) { + err = settings.PHP.ExecScript(ctx, server, &value, settingsPHP, "get_setting", key) return } -func (settings *Settings) Set(server *phpx.Server, key string, value any) error { - return settings.PHP.ExecScript(server, nil, settingsPHP, "set_setting", key, value) +func (settings *Settings) Set(ctx context.Context, server *phpx.Server, key string, value any) error { + return settings.PHP.ExecScript(ctx, server, nil, settingsPHP, "set_setting", key, value) } diff --git a/internal/wisski/ingredient/php/extras/stats.go b/internal/wisski/ingredient/php/extras/stats.go index cddf312..208aa52 100644 --- a/internal/wisski/ingredient/php/extras/stats.go +++ b/internal/wisski/ingredient/php/extras/stats.go @@ -1,8 +1,8 @@ package extras import ( + "context" _ "embed" - "log" "github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/status" @@ -20,11 +20,8 @@ type Stats struct { var statsPHP string // Get fetches all statistics from the server -func (stats *Stats) Get(server *phpx.Server) (data status.Statistics, err error) { - err = stats.PHP.ExecScript(server, &data, statsPHP, "export_statistics") - if err != nil { - log.Println(err) - } +func (stats *Stats) Get(ctx context.Context, server *phpx.Server) (data status.Statistics, err error) { + err = stats.PHP.ExecScript(ctx, server, &data, statsPHP, "export_statistics") return } @@ -33,6 +30,6 @@ func (stats *Stats) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (e return } - info.Statistics, _ = stats.Get(flags.Server) + info.Statistics, _ = stats.Get(flags.Context, flags.Server) return } diff --git a/internal/wisski/ingredient/php/php.go b/internal/wisski/ingredient/php/php.go index 11ba7cd..edb9dcf 100644 --- a/internal/wisski/ingredient/php/php.go +++ b/internal/wisski/ingredient/php/php.go @@ -1,6 +1,7 @@ package php import ( + "context" "strings" "github.com/FAU-CDI/wisski-distillery/internal/phpx" @@ -28,7 +29,7 @@ type PHP struct { // It's arguments are encoded as json using [json.Marshal] and decoded within php. // // The return value of the function is again marshaled with json and returned to the caller. -func (php *PHP) ExecScript(server *phpx.Server, value any, code string, entrypoint string, args ...any) (err error) { +func (php *PHP) ExecScript(ctx context.Context, server *phpx.Server, value any, code string, entrypoint string, args ...any) (err error) { if server == nil { server = php.NewServer() if err != nil { @@ -38,15 +39,15 @@ func (php *PHP) ExecScript(server *phpx.Server, value any, code string, entrypoi } if code != "" { - if err := server.MarshalEval(nil, strings.TrimPrefix(code, "