Add context

This commit adds and passes context around to (almost) every function.
This allows cancelling (almost) every function call globally.
This commit is contained in:
Tom Wiesing 2022-11-28 13:30:08 +01:00
parent 996ecb9f80
commit 3455f491ca
No known key found for this signature in database
104 changed files with 836 additions and 511 deletions

View file

@ -41,12 +41,12 @@ func (bk backup) Run(context wisski_distillery.Context) error {
// prune old backups // prune old backups
if !bk.NoPrune { if !bk.NoPrune {
defer logging.LogOperation(func() error { defer logging.LogOperation(func() error {
return dis.Exporter().PruneExports(context.IOStream) return dis.Exporter().PruneExports(context.Context, context.IOStream)
}, context.IOStream, "Pruning old backups") }, context.IOStream, "Pruning old backups")
} }
// do the handling // 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, Dest: bk.Positionals.Dest,
StagingOnly: bk.StagingOnly, StagingOnly: bk.StagingOnly,

View file

@ -40,7 +40,7 @@ var errBlindUpdateFailed = exit.Error{
func (bu blindUpdate) Run(context wisski_distillery.Context) error { func (bu blindUpdate) Run(context wisski_distillery.Context) error {
// find all the instances! // 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 { if err != nil {
return err return err
} }
@ -52,7 +52,7 @@ func (bu blindUpdate) Run(context wisski_distillery.Context) error {
// and do the actual blind_update! // and do the actual blind_update!
return status.StreamGroup(context.IOStream, bu.Parallel, func(instance *wisski.WissKI, str stream.IOStream) error { 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 { }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("blind_update %q", item.Slug) return fmt.Sprintf("blind_update %q", item.Slug)
})) }))

View file

@ -115,7 +115,7 @@ func (bs cBootstrap) Run(context wisski_distillery.Context) error {
return errBoostrapFailedToCopyExe.WithMessageF(err) return errBoostrapFailedToCopyExe.WithMessageF(err)
} }
err = fsx.CopyFile(env, wdcliPath, exe) err = fsx.CopyFile(context.Context, env, wdcliPath, exe)
if err != nil && err != fsx.ErrCopySameFile { if err != nil && err != fsx.ErrCopySameFile {
return errBoostrapFailedToCopyExe.WithMessageF(err) return errBoostrapFailedToCopyExe.WithMessageF(err)
} }

View file

@ -33,14 +33,14 @@ func (cron) Description() wisski_distillery.Description {
func (cr cron) Run(context wisski_distillery.Context) error { func (cr cron) Run(context wisski_distillery.Context) error {
// find all the instances! // 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 { if err != nil {
return err return err
} }
// and do the actual blind_update! // and do the actual blind_update!
return status.StreamGroup(context.IOStream, cr.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { 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 { }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("cron %q", item.Slug) return fmt.Sprintf("cron %q", item.Slug)
})) }))

View file

@ -40,14 +40,14 @@ var errSettingSet = exit.Error{
} }
func (ds setting) Run(context wisski_distillery.Context) 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 { if err != nil {
return err return err
} }
if ds.Positionals.Value == "" { if ds.Positionals.Value == "" {
// get the setting // 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 { if err != nil {
return errSettingGet.Wrap(err) return errSettingGet.Wrap(err)
} }
@ -69,7 +69,7 @@ func (ds setting) Run(context wisski_distillery.Context) error {
} }
// set the serialized value! // 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) return errSettingSet.Wrap(err)
} }

View file

@ -75,7 +75,7 @@ var errPasswordsNotIdentical = exit.Error{
} }
func (du duser) Run(context wisski_distillery.Context) 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 { if err != nil {
return err 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 { 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 { if err != nil {
return err return err
} }
@ -110,7 +110,7 @@ var errPasswordFound = exit.Error{
func (du duser) checkCommonPassword(context wisski_distillery.Context, instance *wisski.WissKI) error { func (du duser) checkCommonPassword(context wisski_distillery.Context, instance *wisski.WissKI) error {
users := instance.Users() users := instance.Users()
entities, err := users.All(nil) entities, err := users.All(context.Context, nil)
if err != nil { if err != nil {
return err return err
} }
@ -121,19 +121,19 @@ func (du duser) checkCommonPassword(context wisski_distillery.Context, instance
}, },
PrefixAlign: true, PrefixAlign: true,
Handler: func(user wstatus.User, index int, writer io.Writer) error { 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 { if err != nil {
return err return err
} }
defer pv.Close() defer pv.Close()
return pv.CheckDictionary(context.Environment.Context(), writer) return pv.CheckDictionary(context.Context, writer)
}, },
}, entities) }, entities)
} }
func (du duser) checkPasswordInteractive(context wisski_distillery.Context, instance *wisski.WissKI) error { 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 { if err != nil {
return err return err
} }
@ -151,7 +151,7 @@ func (du duser) checkPasswordInteractive(context wisski_distillery.Context, inst
break break
} }
if validator.Check(candidate) { if validator.Check(context.Context, candidate) {
context.Println("check passed") context.Println("check passed")
} else { } else {
context.Println("check did not pass") context.Println("check did not pass")
@ -180,5 +180,5 @@ func (du duser) resetPassword(context wisski_distillery.Context, instance *wissk
return errPasswordsNotIdentical return errPasswordsNotIdentical
} }
return instance.Users().SetPassword(nil, du.Positionals.User, passwd1) return instance.Users().SetPassword(context.Context, nil, du.Positionals.User, passwd1)
} }

View file

@ -29,12 +29,12 @@ func (info) Description() wisski_distillery.Description {
} }
func (i info) Run(context wisski_distillery.Context) error { 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 { if err != nil {
return err return err
} }
info, err := instance.Info().Information(false) info, err := instance.Info().Information(context.Context, false)
if err != nil { if err != nil {
return err return err
} }

View file

@ -46,20 +46,20 @@ var errNotUnlock = exit.Error{
} }
func (l instanceLock) Run(context wisski_distillery.Context) 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 { if err != nil {
return err return err
} }
if l.Unlock { if l.Unlock {
if !instance.Locker().TryUnlock() { if !instance.Locker().TryUnlock(context.Context) {
return errNotUnlock return errNotUnlock
} }
context.Println("unlocked") context.Println("unlocked")
return nil return nil
} }
if !instance.Locker().TryLock() { if !instance.Locker().TryLock(context.Context) {
return locker.Locked return locker.Locked
} }

View file

@ -25,7 +25,7 @@ func (ls) Description() wisski_distillery.Description {
} }
func (l ls) Run(context wisski_distillery.Context) error { 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 { if err != nil {
return err return err
} }

View file

@ -50,7 +50,7 @@ func (mma makeMysqlAccount) Run(context wisski_distillery.Context) error {
return errUnableToReadPassword.WithMessageF(err) 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 return err
} }

View file

@ -32,7 +32,7 @@ func (mysql) Description() wisski_distillery.Description {
} }
func (ms mysql) Run(context wisski_distillery.Context) error { 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 { if err != nil {
return err return err
} }

View file

@ -39,14 +39,14 @@ var errNoPathbuilder = exit.Error{
func (pb pathbuilders) Run(context wisski_distillery.Context) error { func (pb pathbuilders) Run(context wisski_distillery.Context) error {
// get the wisski // 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 { if err != nil {
return err return err
} }
// get all of the pathbuilders // get all of the pathbuilders
if pb.Positionals.Name == "" { if pb.Positionals.Name == "" {
names, err := instance.Pathbuilder().All(nil) names, err := instance.Pathbuilder().All(context.Context, nil)
if err != nil { if err != nil {
return errPathbuilders.WithMessageF(err) return errPathbuilders.WithMessageF(err)
} }
@ -57,7 +57,7 @@ func (pb pathbuilders) Run(context wisski_distillery.Context) error {
} }
// get all the pathbuilders // 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 == "" { if xml == "" {
return errNoPathbuilder.WithMessageF(pb.Positionals.Name) return errNoPathbuilder.WithMessageF(pb.Positionals.Name)
} }

View file

@ -31,12 +31,12 @@ var errPrefixesGeneric = exit.Error{
} }
func (p prefixes) Run(context wisski_distillery.Context) 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 { if err != nil {
return err return err
} }
prefixes, err := instance.Prefixes().All(nil) prefixes, err := instance.Prefixes().All(context.Context, nil)
if err != nil { if err != nil {
return errPrefixesGeneric.Wrap(err) return errPrefixesGeneric.Wrap(err)
} }

View file

@ -45,7 +45,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// check that it doesn't already exist // check that it doesn't already exist
logging.LogMessage(context.IOStream, "Provisioning new WissKI instance %s", slug) 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) return errProvisionAlreadyExists.WithMessageF(slug)
} }
@ -63,7 +63,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// Store in the instances table! // Store in the instances table!
if err := logging.LogOperation(func() error { 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) return errProvisionGeneric.WithMessageF(slug, err)
} }
@ -77,7 +77,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
domain := instance.Domain() domain := instance.Domain()
for _, pc := range dis.Provisionable() { for _, pc := range dis.Provisionable() {
logging.LogMessage(context.IOStream, "Provisioning %s resources", pc.Name()) 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 { if err != nil {
return err return err
} }
@ -90,7 +90,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// run the provision script // run the provision script
if err := logging.LogOperation(func() error { 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) return errProvisionGeneric.WithMessageF(slug, err)
} }
@ -101,7 +101,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// start the container! // start the container!
logging.LogMessage(context.IOStream, "Starting 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 return err
} }

View file

@ -59,7 +59,7 @@ func (p purge) Run(context wisski_distillery.Context) error {
// load the instance (first via bookkeeping, then via defaults) // load the instance (first via bookkeeping, then via defaults)
logging.LogMessage(context.IOStream, "Checking bookkeeping table") 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 { if err == instances.ErrWissKINotFound {
context.Println("Not found in bookkeeping table, assuming defaults") context.Println("Not found in bookkeeping table, assuming defaults")
instance, err = dis.Instances().Create(slug) instance, err = dis.Instances().Create(slug)
@ -70,7 +70,7 @@ func (p purge) Run(context wisski_distillery.Context) error {
// remove docker stack // remove docker stack
logging.LogMessage(context.IOStream, "Stopping and removing docker container") 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) context.EPrintln(err)
} }
@ -85,7 +85,7 @@ func (p purge) Run(context wisski_distillery.Context) error {
domain := instance.Domain() domain := instance.Domain()
for _, pc := range dis.Provisionable() { for _, pc := range dis.Provisionable() {
logging.LogMessage(context.IOStream, "Purging %s resources", pc.Name()) 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 { if err != nil {
return err return err
} }
@ -98,13 +98,13 @@ func (p purge) Run(context wisski_distillery.Context) error {
// remove from bookkeeping // remove from bookkeeping
logging.LogMessage(context.IOStream, "Removing instance 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) context.EPrintln(err)
} }
// remove the filesystem // remove the filesystem
logging.LogMessage(context.IOStream, "Remove lock data") logging.LogMessage(context.IOStream, "Remove lock data")
if instance.Locker().TryUnlock() { if instance.Locker().TryUnlock(context.Context) {
context.EPrintln("instance was not locked") context.EPrintln("instance was not locked")
} }

View file

@ -40,14 +40,14 @@ func (rb rebuild) Run(context wisski_distillery.Context) error {
dis := context.Environment dis := context.Environment
// find the instances // find the instances
wissKIs, err := dis.Instances().Load(rb.Positionals.Slug...) wissKIs, err := dis.Instances().Load(context.Context, rb.Positionals.Slug...)
if err != nil { if err != nil {
return err return err
} }
// and do the actual rebuild // and do the actual rebuild
return status.StreamGroup(context.IOStream, rb.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { 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 { }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("rebuild %q", item.Slug) return fmt.Sprintf("rebuild %q", item.Slug)
})) }))

View file

@ -46,7 +46,7 @@ func (r reserve) Run(context wisski_distillery.Context) error {
// check that it doesn't already exist // check that it doesn't already exist
logging.LogMessage(context.IOStream, "Reserving new WissKI instance %s", slug) 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) return errProvisionAlreadyExists.WithMessageF(slug)
} }
@ -66,13 +66,13 @@ func (r reserve) Run(context wisski_distillery.Context) error {
s := instance.Reserve().Stack() s := instance.Reserve().Stack()
{ {
if err := logging.LogOperation(func() error { 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 { }, context.IOStream, "Installing docker stack"); err != nil {
return err return err
} }
if err := logging.LogOperation(func() error { 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 { }, context.IOStream, "Updating docker stack"); err != nil {
return err return err
} }

View file

@ -5,6 +5,7 @@ import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/cli" "github.com/FAU-CDI/wisski-distillery/internal/cli"
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
) )
@ -33,7 +34,7 @@ var errServerListen = exit.Error{
func (s server) Run(context wisski_distillery.Context) error { func (s server) Run(context wisski_distillery.Context) error {
dis := context.Environment dis := context.Environment
handler, err := dis.Control().Server(dis.Context(), context.IOStream) handler, err := dis.Control().Server(context.Context, context.IOStream)
if err != nil { if err != nil {
return err return err
} }
@ -47,14 +48,21 @@ func (s server) Run(context wisski_distillery.Context) error {
} }
go func() { go func() {
<-dis.Context().Done() <-context.Context.Done()
listener.Close() listener.Close()
}() }()
// and serve that listener server := http.Server{
err = http.Serve(listener, http.StripPrefix(s.Prefix, handler)) Handler: http.StripPrefix(s.Prefix, handler),
if err == nil {
return nil
} }
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) return errServerListen.Wrap(err)
} }

View file

@ -38,12 +38,12 @@ var errShell = exit.Error{
} }
func (sh shell) Run(context wisski_distillery.Context) 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 { if err != nil {
return err 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 { if err != nil {
return errShell.WithMessageF(err) return errShell.WithMessageF(err)
} }

View file

@ -39,13 +39,13 @@ func (sn snapshot) Run(context wisski_distillery.Context) error {
dis := context.Environment dis := context.Environment
// find the instance! // find the instance!
instance, err := dis.Instances().WissKI(sn.Positionals.Slug) instance, err := dis.Instances().WissKI(context.Context, sn.Positionals.Slug)
if err != nil { if err != nil {
return err return err
} }
// do a snapshot of it! // 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, Dest: sn.Positionals.Dest,
StagingOnly: sn.StagingOnly, StagingOnly: sn.StagingOnly,

View file

@ -31,7 +31,7 @@ var errSSHListen = exit.Error{
func (s ssh) Run(context wisski_distillery.Context) error { func (s ssh) Run(context wisski_distillery.Context) error {
dis := context.Environment 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 { if err != nil {
return err return err
} }
@ -45,7 +45,7 @@ func (s ssh) Run(context wisski_distillery.Context) error {
} }
go func() { go func() {
<-dis.Context().Done() <-context.Context.Done()
listener.Close() listener.Close()
}() }()

View file

@ -25,7 +25,7 @@ func (cStatus) Description() wisski_distillery.Description {
} }
func (s cStatus) Run(context wisski_distillery.Context) error { 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 { if err != nil {
return err return err
} }

View file

@ -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 { Handler: func(item component.Installable, index int, writer io.Writer) error {
io := stream.NewIOStream(writer, writer, stream.Null, 0) 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 { }, dis.Installable()); err != nil {
return err return err
@ -74,7 +74,7 @@ func (sp systempause) start(context wisski_distillery.Context, dis *dis.Distille
logging.LogMessage(context.IOStream, "Starting Up WissKIs") logging.LogMessage(context.IOStream, "Starting Up WissKIs")
// find the instances // find the instances
wissKIs, err := dis.Instances().All() wissKIs, err := dis.Instances().All(context.Context)
if err != nil { if err != nil {
return err 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 { Handler: func(item *wisski.WissKI, index int, writer io.Writer) error {
io := stream.NewIOStream(writer, writer, stream.Null, 0) 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 { }, wissKIs); err != nil {
return err return err
@ -101,7 +101,7 @@ func (sp systempause) stop(context wisski_distillery.Context, dis *dis.Distiller
logging.LogMessage(context.IOStream, "Shutting Down WissKIs") logging.LogMessage(context.IOStream, "Shutting Down WissKIs")
// find the instances // find the instances
wissKIs, err := dis.Instances().All() wissKIs, err := dis.Instances().All(context.Context)
if err != nil { if err != nil {
return err 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 { Handler: func(item *wisski.WissKI, index int, writer io.Writer) error {
io := stream.NewIOStream(writer, writer, stream.Null, 0) 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 { }, wissKIs); err != nil {
return err 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 { Handler: func(item component.Installable, index int, writer io.Writer) error {
io := stream.NewIOStream(writer, writer, stream.Null, 0) 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 { }, dis.Installable()); err != nil {
return err return err

View file

@ -135,11 +135,11 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
io := stream.NewIOStream(writer, writer, stream.Null, 0) io := stream.NewIOStream(writer, writer, stream.Null, 0)
stack := item.Stack(context.Environment.Environment) 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 return err
} }
if err := stack.Update(io, true); err != nil { if err := stack.Update(context.Context, io, true); err != nil {
return err return err
} }
@ -154,7 +154,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
updated[item.ID()] = struct{}{} updated[item.ID()] = struct{}{}
}() }()
return ud.Update(io) return ud.Update(context.Context, io)
}, },
}, dis.Installable()) }, dis.Installable())
}, context.IOStream, "Performing Stack Updates"); err != nil { }, context.IOStream, "Performing Stack Updates"); err != nil {
@ -170,7 +170,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
context.Println("Already updated") context.Println("Already updated")
return nil return nil
} }
return item.Update(context.IOStream) return item.Update(context.Context, context.IOStream)
}, context.IOStream, "Updating Component: %s", name); err != nil { }, context.IOStream, "Updating Component: %s", name); err != nil {
return errBootstrapComponent.WithMessageF(name, err) 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 { func (si systemupdate) mustExec(context wisski_distillery.Context, workdir string, exe string, argv ...string) error {
dis := context.Environment dis := context.Environment
if workdir == "" { 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 { if code != 0 {
err := errMustExecFailed.WithMessageF(code) err := errMustExecFailed.WithMessageF(code)
err.ExitCode = exit.ExitCode(code) err.ExitCode = exit.ExitCode(code)

View file

@ -37,14 +37,14 @@ var errPrefixUpdateFailed = exit.Error{
func (upc updateprefixconfig) Run(context wisski_distillery.Context) error { func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
dis := context.Environment dis := context.Environment
wissKIs, err := dis.Instances().All() wissKIs, err := dis.Instances().All(context.Context)
if err != nil { if err != nil {
return errPrefixUpdateFailed.Wrap(err) return errPrefixUpdateFailed.Wrap(err)
} }
return status.StreamGroup(context.IOStream, upc.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { return status.StreamGroup(context.IOStream, upc.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error {
io.Println("reading prefixes") io.Println("reading prefixes")
err := instance.Prefixes().Update() err := instance.Prefixes().Update(context.Context)
if err != nil { if err != nil {
return errPrefixUpdateFailed.Wrap(err) return errPrefixUpdateFailed.Wrap(err)
} }

10
go.mod
View file

@ -12,12 +12,12 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/pkg/errors v0.9.1 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/crypto v0.3.0
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 golang.org/x/sync v0.1.0
gorm.io/driver/mysql v1.3.6 gorm.io/driver/mysql v1.4.4
gorm.io/gorm v1.23.10 gorm.io/gorm v1.24.2
) )
require ( require (

10
go.sum
View file

@ -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/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 h1:gamK9OuRqoX2yQlA/nkgfVHHZWd/u2uUj6vJMYrYa70=
github.com/tkw1536/goprogram v0.1.1/go.mod h1:Jqs0sTMzhrAGCX3JQrlEwQ0WRWQACCvuQQkaBDp65pE= 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.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 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 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 h1:fGZugkZk2UgYBxtpKmvub51Yno1LJDeEsRp2xGD+0gY=
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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= 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 h1:BhX1Y/RyALb+T9bZ3t07wLnPZBukt+IRkMn8UZSNbGM=
gorm.io/driver/mysql v1.3.6/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= 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.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.10 h1:4Ne9ZbzID9GUxRkllxN4WjJKpsHx8YbKvekVdgyWh24= gorm.io/gorm v1.23.10 h1:4Ne9ZbzID9GUxRkllxN4WjJKpsHx8YbKvekVdgyWh24=
gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 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=

View file

@ -1,6 +1,7 @@
package component package component
import ( import (
"context"
"io" "io"
"path/filepath" "path/filepath"
@ -45,7 +46,7 @@ type StagingContext interface {
// Passing the empty path creates the destination as a directory. // Passing the empty path creates the destination as a directory.
// //
// It then allows op to fill the file. // 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 copies a file from src to dst.
CopyFile(dst, src string) error CopyFile(dst, src string) error
@ -61,12 +62,13 @@ type StagingContext interface {
// The op function must not retain file. // The op function must not retain file.
// The underlying file does not need to be closed. // The underlying file does not need to be closed.
// AddFile will not return before op has returned. // 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] // 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{ return &stagingContext{
ctx: ctx,
env: env, env: env,
io: io, io: io,
path: path, path: path,
@ -76,6 +78,7 @@ func NewStagingContext(env environment.Environment, io stream.IOStream, path str
// stagingContext implements [components.StagingContext] // stagingContext implements [components.StagingContext]
type stagingContext struct { type stagingContext struct {
ctx context.Context
env environment.Environment // environment env environment.Environment // environment
io stream.IOStream // context the files are sent to io stream.IOStream // context the files are sent to
path string // path to send files 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 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! // resolve the path!
dst, err := sc.resolve(path) dst, err := sc.resolve(path)
if err != nil { if err != nil {
@ -126,30 +134,43 @@ func (sc *stagingContext) AddDirectory(path string, op func() error) error {
sc.sendPath(path) sc.sendPath(path)
// and run the files! // and run the files!
return op() return op(sc.ctx)
} }
func (sc *stagingContext) CopyFile(dst, src string) error { func (sc *stagingContext) CopyFile(dst, src string) error {
if err, ok := sc.ctxdone(); ok {
return err
}
dstPath, err := sc.resolve(dst) dstPath, err := sc.resolve(dst)
if err != nil { if err != nil {
return err return err
} }
sc.sendPath(dst) 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 { func (sc *stagingContext) CopyDirectory(dst, src string) error {
if err, ok := sc.ctxdone(); ok {
return err
}
dstPath, err := sc.resolve(dst) dstPath, err := sc.resolve(dst)
if err != nil { if err != nil {
return err 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) 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! // resolve the path!
dst, err := sc.resolve(path) dst, err := sc.resolve(path)
if err != nil { if err != nil {
@ -167,5 +188,11 @@ func (sc *stagingContext) AddFile(path string, op func(file io.Writer) error) er
sc.sendPath(path) sc.sendPath(path)
// and do whatever they wanted to do // 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
} }

View file

@ -26,10 +26,10 @@ type Home struct {
func (*Home) Routes() []string { return []string{"/"} } func (*Home) Routes() []string { return []string{"/"} }
func (home *Home) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) { func (home *Home) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) {
home.updateRedirect(context, io) home.updateRedirect(ctx, io)
home.updateInstances(context, io) home.updateInstances(ctx, io)
home.updateRender(context, io) home.updateRender(ctx, io)
return home, nil return home, nil
} }

View file

@ -19,14 +19,27 @@ func (home *Home) updateInstances(ctx context.Context, io stream.IOStream) {
for t := range timex.TickContext(ctx, home.RefreshInterval) { for t := range timex.TickContext(ctx, home.RefreshInterval) {
io.Printf("[%s]: reloading instance list\n", t.Format(time.Stamp)) io.Printf("[%s]: reloading instance list\n", t.Format(time.Stamp))
names, _ := home.instanceMap() err := (func() error {
home.instanceNames.Set(names) 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) { func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) {
wissKIs, err := home.Instances.All() wissKIs, err := home.Instances.All(ctx)
if err != nil { if err != nil {
return nil, err 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) { func (home *Home) updateRender(ctx context.Context, io stream.IOStream) {
go func() { go func() {
for t := range timex.TickContext(ctx, home.RefreshInterval) { 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() err := (func() error {
home.homeBytes.Set(bytes) 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 homeHTMLStr string
var homeTemplate = static.AssetsHomeHome.MustParseShared("home.html", homeHTMLStr) 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 var context HomeContext
// setup a couple of static things // setup a couple of static things
@ -61,7 +87,7 @@ func (home *Home) homeRender() ([]byte, error) {
context.SelfRedirect = home.Config.SelfRedirect.String() context.SelfRedirect = home.Config.SelfRedirect.String()
// find all the WissKIs // find all the WissKIs
wissKIs, err := home.Instances.All() wissKIs, err := home.Instances.All(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -73,7 +99,7 @@ func (home *Home) homeRender() ([]byte, error) {
i := i i := i
wissKI := instance wissKI := instance
eg.Go(func() (err error) { eg.Go(func() (err error) {
context.Instances[i], err = wissKI.Info().Information(false) context.Instances[i], err = wissKI.Info().Information(ctx, false)
return return
}) })
} }

View file

@ -16,13 +16,27 @@ func (home *Home) updateRedirect(ctx context.Context, io stream.IOStream) {
for t := range timex.TickContext(ctx, home.RefreshInterval) { for t := range timex.TickContext(ctx, home.RefreshInterval) {
io.Printf("[%s]: reloading overrides\n", t.Format(time.Stamp)) io.Printf("[%s]: reloading overrides\n", t.Format(time.Stamp))
redirect, _ := home.loadRedirect() err := (func() error {
home.redirect.Set(&redirect) 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 { if redirect.Overrides == nil {
redirect.Overrides = make(map[string]string) redirect.Overrides = make(map[string]string)
} }

View file

@ -52,7 +52,7 @@ func (info *Info) ingredients(r *http.Request) (cp ingredientsContext, err error
cp.Time = time.Now().UTC() cp.Time = time.Now().UTC()
// find the instance itself! // 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 { if err == instances.ErrWissKINotFound {
return cp, httpx.ErrNotFound return cp, httpx.ErrNotFound
} }

View file

@ -1,6 +1,7 @@
package info package info
import ( import (
"context"
"net/http" "net/http"
"time" "time"
@ -25,18 +26,18 @@ type indexContext struct {
} }
func (info *Info) index(r *http.Request) (idx indexContext, err error) { 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 return
} }
// Status produces a new observation of the distillery, and a new information of all instances // 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. // 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 var group errgroup.Group
group.Go(func() error { group.Go(func() error {
// list all the instances // list all the instances
all, err := info.Instances.All() all, err := info.Instances.All(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -50,7 +51,7 @@ func (info *Info) Status(QuickInformation bool) (target status.Distillery, infor
// store the info for this group! // store the info for this group!
group.Go(func() (err error) { group.Go(func() (err error) {
information[i], err = instance.Info().Information(true) information[i], err = instance.Info().Information(ctx, true)
return err return err
}) })
} }
@ -59,7 +60,9 @@ func (info *Info) Status(QuickInformation bool) (target status.Distillery, infor
}) })
// gather all the observations // gather all the observations
var flags component.FetcherFlags flags := component.FetcherFlags{
Context: ctx,
}
for _, o := range info.Fetchers { for _, o := range info.Fetchers {
o := o o := o
group.Go(func() error { group.Go(func() error {

View file

@ -28,11 +28,11 @@ type Info struct {
func (*Info) Routes() []string { return []string{"/dis/"} } 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() router := mux.NewRouter()
{ {
socket := &httpx.WebSocket{ socket := &httpx.WebSocket{
Context: context, Context: ctx,
Fallback: router, Fallback: router,
Handler: info.serveSocket, Handler: info.serveSocket,
} }
@ -82,12 +82,12 @@ func (info *Info) Handler(route string, context context.Context, io stream.IOStr
} }
// get the instance // get the instance
instance, err := info.Instances.WissKI(r.PostFormValue("slug")) instance, err := info.Instances.WissKI(r.Context(), r.PostFormValue("slug"))
if err != nil { if err != nil {
return "", httpx.ErrNotFound 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 { if err != nil {
return "", err return "", err
} }

View file

@ -29,7 +29,7 @@ type instanceContext struct {
func (info *Info) instance(r *http.Request) (is instanceContext, err error) { func (info *Info) instance(r *http.Request) (is instanceContext, err error) {
// find the instance itself! // 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 { if err == instances.ErrWissKINotFound {
return is, httpx.ErrNotFound return is, httpx.ErrNotFound
} }
@ -39,7 +39,7 @@ func (info *Info) instance(r *http.Request) (is instanceContext, err error) {
is.Instance = instance.Instance is.Instance = instance.Instance
// get some more info about the wisski // 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 { if err != nil {
return is, err return is, err
} }

View file

@ -1,6 +1,7 @@
package info package info
import ( import (
"context"
"encoding/json" "encoding/json"
"time" "time"
@ -14,14 +15,15 @@ import (
type InstanceAction struct { type InstanceAction struct {
NumParams int NumParams int
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
HandleResult func(info *Info, instance *wisski.WissKI, params ...string) (value any, err error) HandleResult func(ctx context.Context, info *Info, instance *wisski.WissKI, params ...string) (value any, err error)
} }
var socketInstanceActions = map[string]InstanceAction{ var socketInstanceActions = map[string]InstanceAction{
"snapshot": { "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( return info.Exporter.MakeExport(
ctx,
str, str,
exporter.ExportTask{ exporter.ExportTask{
Dest: "", Dest: "",
@ -33,18 +35,18 @@ var socketInstanceActions = map[string]InstanceAction{
}, },
}, },
"rebuild": { "rebuild": {
HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { HandleInteractive: func(ctx context.Context, _ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return instance.Barrel().Build(str, true) return instance.Barrel().Build(ctx, str, true)
}, },
}, },
"update": { "update": {
HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { HandleInteractive: func(ctx context.Context, _ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return instance.Drush().Update(str) return instance.Drush().Update(ctx, str)
}, },
}, },
"cron": { "cron": {
HandleInteractive: func(_ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error { HandleInteractive: func(ctx context.Context, _ *Info, instance *wisski.WissKI, str stream.IOStream, params ...string) error {
return instance.Drush().Cron(str) return instance.Drush().Cron(ctx, str)
}, },
}, },
} }
@ -75,7 +77,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In
} }
// resolve the instance // resolve the instance
instance, err := info.Instances.WissKI(string(slug.Bytes)) instance, err := info.Instances.WissKI(conn.Context(), string(slug.Bytes))
if err != nil { if err != nil {
<-conn.WriteText("Instance not found") <-conn.WriteText("Instance not found")
return return
@ -110,7 +112,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In
// handle the interactive action // handle the interactive action
if action.HandleInteractive != nil { if action.HandleInteractive != nil {
err := action.HandleInteractive(info, instance, str, params...) err := action.HandleInteractive(conn.Context(), info, instance, str, params...)
if err != nil { if err != nil {
str.EPrintln(err) str.EPrintln(err)
return return
@ -120,7 +122,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In
// handle the result computation // handle the result computation
if action.HandleResult != nil { if action.HandleResult != nil {
result, err := action.HandleResult(info, instance, params...) result, err := action.HandleResult(conn.Context(), info, instance, params...)
if err != nil { if err != nil {
str.Println("false") str.Println("false")
return return

View file

@ -11,7 +11,7 @@ import (
// The server may spawn background tasks, but these should be terminated once context closes. // The server may spawn background tasks, but these should be terminated once context closes.
// //
// Logging messages are directed to io. // 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 // create a new mux
mux := http.NewServeMux() mux := http.NewServeMux()
@ -19,7 +19,7 @@ func (control *Control) Server(context context.Context, io stream.IOStream) (*ht
for _, s := range control.Servables { for _, s := range control.Servables {
for _, route := range s.Routes() { for _, route := range s.Routes() {
io.Printf("mounting %s\n", route) io.Printf("mounting %s\n", route)
handler, err := s.Handler(route, context, io) handler, err := s.Handler(ctx, route, io)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -20,7 +20,7 @@ func (*Static) Routes() []string { return []string{"/static/"} }
//go:embed dist //go:embed dist
var staticFS embed.FS 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 // take the filesystem
fs, err := fs.Sub(staticFS, "dist") fs, err := fs.Sub(staticFS, "dist")
if err != nil { if err != nil {

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"path/filepath" "path/filepath"
@ -49,7 +50,7 @@ type BackupDescription struct {
} }
// New create a new Backup // 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 backup.Description = description
// catch anything critical that happened during the snapshot // 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! // do the create keeping track of time!
logging.LogOperation(func() error { logging.LogOperation(func() error {
backup.StartTime = time.Now().UTC() backup.StartTime = time.Now().UTC()
backup.run(io, exporter) backup.run(ctx, io, exporter)
backup.EndTime = time.Now().UTC() backup.EndTime = time.Now().UTC()
return nil return nil
@ -69,7 +70,7 @@ func (exporter *Exporter) NewBackup(io stream.IOStream, description BackupDescri
return 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 // create a manifest
manifest, done := backup.handleManifest(backup.Description.Dest) manifest, done := backup.handleManifest(backup.Description.Dest)
defer done() 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 { Handler: func(bc component.Backupable, index int, writer io.Writer) error {
return bc.Backup( return bc.Backup(
component.NewStagingContext( component.NewStagingContext(
ctx,
exporter.Environment, exporter.Environment,
stream.NewIOStream(writer, writer, nil, 0), stream.NewIOStream(writer, writer, nil, 0),
filepath.Join(backup.Description.Dest, bc.BackupName()), filepath.Join(backup.Description.Dest, bc.BackupName()),
@ -124,7 +126,7 @@ func (backup *Backup) run(ios stream.IOStream, exporter *Exporter) {
} }
// list all instances // list all instances
wissKIs, err := exporter.Instances.All() wissKIs, err := exporter.Instances.All(ctx)
if err != nil { if err != nil {
backup.InstanceListErr = err backup.InstanceListErr = err
return nil return nil
@ -147,7 +149,7 @@ func (backup *Backup) run(ios stream.IOStream, exporter *Exporter) {
manifest <- dir 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, Dest: dir,
}) })
}, },

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"fmt" "fmt"
"io" "io"
@ -19,8 +20,8 @@ func (Bookkeeping) SnapshotNeedsRunning() bool { return false }
func (Bookkeeping) SnapshotName() string { return "bookkeeping.txt" } func (Bookkeeping) SnapshotName() string { return "bookkeeping.txt" }
// Snapshot creates a snapshot of this instance // Snapshot creates a snapshot of this instance
func (*Bookkeeping) Snapshot(wisski models.Instance, context component.StagingContext) error { func (*Bookkeeping) Snapshot(wisski models.Instance, scontext component.StagingContext) error {
return context.AddFile(".", func(file io.Writer) error { return scontext.AddFile(".", func(ctx context.Context, file io.Writer) error {
_, err := fmt.Fprintf(file, "%#v\n", wisski) _, err := fmt.Fprintf(file, "%#v\n", wisski)
return err return err
}) })

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
@ -15,13 +16,13 @@ func (*Config) BackupName() string {
return "config" return "config"
} }
func (control *Config) Backup(context component.StagingContext) error { func (control *Config) Backup(scontext component.StagingContext) error {
files := control.backupFiles() files := control.backupFiles()
return context.AddDirectory("", func() error { return scontext.AddDirectory("", func(ctx context.Context) error {
for _, src := range files { for _, src := range files {
name := filepath.Base(src) name := filepath.Base(src)
if err := context.CopyFile(name, src); err != nil { if err := scontext.CopyFile(name, src); err != nil {
return err return err
} }
} }

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"io" "io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "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 (Pathbuilders) SnapshotName() string { return "pathbuilders" }
func (pbs *Pathbuilders) Snapshot(wisski models.Instance, context component.StagingContext) error { func (pbs *Pathbuilders) Snapshot(wisski models.Instance, scontext component.StagingContext) error {
return context.AddDirectory(".", func() error { return scontext.AddDirectory(".", func(ctx context.Context) error {
builders, err := pbs.Instances.Instance(wisski).Pathbuilder().GetAll(nil) builders, err := pbs.Instances.Instance(ctx, wisski).Pathbuilder().GetAll(ctx, nil)
if err != nil { if err != nil {
return err return err
} }
for name, bytes := range builders { 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)) _, err := file.Write([]byte(bytes))
return err return err
}); err != nil { }); err != nil {

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"io" "io"
"path/filepath" "path/filepath"
@ -42,7 +43,7 @@ type export interface {
// MakeExport performs an export task as described by flags. // MakeExport performs an export task as described by flags.
// Output is directed to the provided io. // 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 // extract parameters
Title := "Backup" Title := "Backup"
Slug := "" Slug := ""
@ -95,11 +96,11 @@ func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err e
var sl export var sl export
if task.Instance == nil { if task.Instance == nil {
task.BackupDescription.Dest = stagingDir task.BackupDescription.Dest = stagingDir
backup := exporter.NewBackup(io, task.BackupDescription) backup := exporter.NewBackup(ctx, io, task.BackupDescription)
sl = &backup sl = &backup
} else { } else {
task.SnapshotDescription.Dest = stagingDir task.SnapshotDescription.Dest = stagingDir
snapshot := exporter.NewSnapshot(task.Instance, io, task.SnapshotDescription) snapshot := exporter.NewSnapshot(ctx, task.Instance, io, task.SnapshotDescription)
sl = &snapshot sl = &snapshot
} }
@ -131,7 +132,7 @@ func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err e
// write out the log entry // write out the log entry
entry.Path = stagingDir entry.Path = stagingDir
entry.Packed = false entry.Packed = false
exporter.ExporterLogger.Add(entry) exporter.ExporterLogger.Add(ctx, entry)
io.Printf("Wrote %s\n", stagingDir) io.Printf("Wrote %s\n", stagingDir)
return nil return nil
@ -159,7 +160,7 @@ func (exporter *Exporter) MakeExport(io stream.IOStream, task ExportTask) (err e
logging.LogMessage(io, "Writing Log Entry") logging.LogMessage(io, "Writing Log Entry")
entry.Path = archivePath entry.Path = archivePath
entry.Packed = true entry.Packed = true
exporter.ExporterLogger.Add(entry) exporter.ExporterLogger.Add(ctx, entry)
// and we're done! // and we're done!
return nil return nil

View file

@ -1,6 +1,8 @@
package logger package logger
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "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/dis/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/models"
@ -19,8 +21,8 @@ type Logger struct {
// For retrieves (and prunes) the ExportLog. // For retrieves (and prunes) the ExportLog.
// Slug determines if entries for Backups (empty slug) // Slug determines if entries for Backups (empty slug)
// or a specific Instance (non-empty slug) are returned. // or a specific Instance (non-empty slug) are returned.
func (log *Logger) For(slug string) (exports []models.Export, err error) { func (log *Logger) For(ctx context.Context, slug string) (exports []models.Export, err error) {
exports, err = log.Log() exports, err = log.Log(ctx)
if err != nil { if err != nil {
return nil, err 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. // 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! // query the table!
table, err := log.SQL.QueryTable(false, models.ExportTable) table, err := log.SQL.QueryTable(ctx, false, models.ExportTable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -63,9 +65,9 @@ func (log *Logger) Log() ([]models.Export, error) {
} }
// AddToExportLog adds the provided export to the log. // 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 // find the table
table, err := log.SQL.QueryTable(false, models.ExportTable) table, err := log.SQL.QueryTable(ctx, false, models.ExportTable)
if err != nil { if err != nil {
return err return err
} }
@ -79,7 +81,7 @@ func (log *Logger) Add(export models.Export) error {
} }
// Fetch writes the SnapshotLog into the given observation // Fetch writes the SnapshotLog into the given observation
func (logger *Logger) Fetch(flags component.FetcherFlags, target *status.Distillery) (err error) { func (logger *Logger) Fetch(ctx context.Context, flags component.FetcherFlags, target *status.Distillery) (err error) {
target.Backups, err = logger.For("") target.Backups, err = logger.For(ctx, "")
return return
} }

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"path/filepath" "path/filepath"
"time" "time"
@ -14,7 +15,7 @@ func (exporter *Exporter) ShouldPrune(modtime time.Time) bool {
} }
// Prune prunes all old exports // 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() sPath := exporter.ArchivePath()
// list all the files // list all the files
@ -50,6 +51,6 @@ func (exporter *Exporter) PruneExports(io stream.IOStream) error {
} }
// prune the snapshot log! // prune the snapshot log!
_, err = exporter.ExporterLogger.Log() _, err = exporter.ExporterLogger.Log(ctx)
return err return err
} }

View file

@ -1,6 +1,7 @@
package exporter package exporter
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"path/filepath" "path/filepath"
@ -44,10 +45,10 @@ type Snapshot struct {
} }
// Snapshot creates a new snapshot of this instance into dest // 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") logging.LogMessage(io, "Locking instance")
if !instance.Locker().TryLock() { if !instance.Locker().TryLock(ctx) {
err := locker.Locked err := locker.Locked
io.EPrintln(err) io.EPrintln(err)
logging.LogMessage(io, "Aborting snapshot creation") logging.LogMessage(io, "Aborting snapshot creation")
@ -58,7 +59,7 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre
} }
defer func() { defer func() {
logging.LogMessage(io, "Unlocking instance") logging.LogMessage(io, "Unlocking instance")
instance.Locker().Unlock() instance.Locker().Unlock(ctx)
}() }()
// setup the snapshot // setup the snapshot
@ -74,8 +75,8 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre
logging.LogOperation(func() error { logging.LogOperation(func() error {
snapshot.StartTime = time.Now().UTC() snapshot.StartTime = time.Now().UTC()
snapshot.ErrWhitebox = snapshot.makeParts(io, snapshots, instance, false) snapshot.ErrWhitebox = snapshot.makeParts(ctx, io, snapshots, instance, false)
snapshot.ErrBlackbox = snapshot.makeParts(io, snapshots, instance, true) snapshot.ErrBlackbox = snapshot.makeParts(ctx, io, snapshots, instance, true)
snapshot.EndTime = time.Now().UTC() snapshot.EndTime = time.Now().UTC()
return nil return nil
@ -85,16 +86,16 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre
return 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 { if !needsRunning && !snapshot.Description.Keepalive {
stack := instance.Barrel().Stack() stack := instance.Barrel().Stack()
logging.LogMessage(ios, "Stopping instance") logging.LogMessage(ios, "Stopping instance")
snapshot.ErrStop = stack.Down(ios) snapshot.ErrStop = stack.Down(ctx, ios)
defer func() { defer func() {
logging.LogMessage(ios, "Starting instance") logging.LogMessage(ios, "Starting instance")
snapshot.ErrStart = stack.Up(ios) snapshot.ErrStart = stack.Up(ctx, ios)
}() }()
} }
// handle writing the manifest! // handle writing the manifest!
@ -123,6 +124,7 @@ func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Exporter, in
return sc.Snapshot( return sc.Snapshot(
instance.Instance, instance.Instance,
component.NewStagingContext( component.NewStagingContext(
ctx,
snapshots.Environment, snapshots.Environment,
stream.NewIOStream(writer, writer, nil, 0), stream.NewIOStream(writer, writer, nil, 0),
filepath.Join(snapshot.Description.Dest, sc.SnapshotName()), filepath.Join(snapshot.Description.Dest, sc.SnapshotName()),

View file

@ -1,6 +1,10 @@
package component package component
import "github.com/FAU-CDI/wisski-distillery/internal/status" import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/status"
)
type DistilleryFetcher interface { type DistilleryFetcher interface {
Component Component
@ -11,4 +15,6 @@ type DistilleryFetcher interface {
} }
// FetcherFlags describes options for a DistilleryFetcher // FetcherFlags describes options for a DistilleryFetcher
type FetcherFlags struct{} type FetcherFlags struct {
Context context.Context
}

View file

@ -1,6 +1,8 @@
package component package component
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
@ -40,5 +42,5 @@ type Updatable interface {
// It may send output to the provided stream. // It may send output to the provided stream.
// //
// Updating should be idempotent, meaning running it multiple times must not break the existing system. // 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
} }

View file

@ -1,6 +1,7 @@
package instances package instances
import ( import (
"context"
"errors" "errors"
"path/filepath" "path/filepath"
@ -42,13 +43,13 @@ func (instances *Instances) use(wisski *wisski.WissKI) {
// WissKI returns the WissKI with the provided slug, if it exists. // WissKI returns the WissKI with the provided slug, if it exists.
// It the WissKI does not exist, returns ErrWissKINotFound. // 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 sql := instances.SQL
if err := sql.WaitQueryTable(); err != nil { if err := sql.WaitQueryTable(ctx); err != nil {
return nil, err return nil, err
} }
table, err := sql.QueryTable(false, models.InstanceTable) table, err := sql.QueryTable(ctx, false, models.InstanceTable)
if err != nil { if err != nil {
return nil, err 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. // Instance is a convenience function to return an instance based on a model slug.
// When the instance does not exist, returns nil. // When the instance does not exist, returns nil.
func (instances *Instances) Instance(instance models.Instance) *wisski.WissKI { func (instances *Instances) Instance(ctx context.Context, instance models.Instance) *wisski.WissKI {
wissKI, err := instances.WissKI(instance.Slug) wissKI, err := instances.WissKI(ctx, instance.Slug)
if err != nil { if err != nil {
return 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. // Has checks if a WissKI with the provided slug exists inside the database.
// It does not perform any checks on the WissKI itself. // 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 sql := instances.SQL
if err := sql.WaitQueryTable(); err != nil { if err := sql.WaitQueryTable(ctx); err != nil {
return false, err return false, err
} }
table, err := sql.QueryTable(false, models.InstanceTable) table, err := sql.QueryTable(ctx, false, models.InstanceTable)
if err != nil { if err != nil {
return false, err 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. // 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. // 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) { func (instances *Instances) All(ctx context.Context) ([]*wisski.WissKI, error) {
return instances.find(true, func(table *gorm.DB) *gorm.DB { return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB {
return table return table
}) })
} }
// WissKIs returns the WissKI instances with the provides slugs. // WissKIs returns the WissKI instances with the provides slugs.
// If a slug does not exist, it is omitted from the result. // If a slug does not exist, it is omitted from the result.
func (instances *Instances) WissKIs(slugs ...string) ([]*wisski.WissKI, error) { func (instances *Instances) WissKIs(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) {
return instances.find(true, func(table *gorm.DB) *gorm.DB { return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB {
return table.Where("slug IN ?", slugs) return table.Where("slug IN ?", slugs)
}) })
} }
// Load is like All, except that when no slugs are provided, it calls All. // 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 { 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 // 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 sql := instances.SQL
if err := sql.WaitQueryTable(); err != nil { if err := sql.WaitQueryTable(ctx); err != nil {
return nil, err return nil, err
} }
// open the bookkeeping table // open the bookkeeping table
table, err := sql.QueryTable(false, models.InstanceTable) table, err := sql.QueryTable(ctx, false, models.InstanceTable)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -1,6 +1,7 @@
package instances package instances
import ( import (
"context"
"embed" "embed"
"github.com/FAU-CDI/wisski-distillery/pkg/unpack" "github.com/FAU-CDI/wisski-distillery/pkg/unpack"
@ -19,7 +20,7 @@ var errBootstrapFailedRuntime = exit.Error{
var runtimeResources embed.FS var runtimeResources embed.FS
// Update installs or updates runtime components needed by this component. // 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) { err := unpack.InstallDir(instances.Still.Environment, instances.Config.RuntimeDir(), "runtime", runtimeResources, func(dst, src string) {
stream.Printf("[copy] %s\n", dst) stream.Printf("[copy] %s\n", dst)
}) })

View file

@ -1,14 +1,18 @@
package meta 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. // Provision provisions new meta storage for this instance.
// NOTE(twiesing): This is a no-op, because we implement Purge. // 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 return nil
} }
// Purge purges the storage for the given instance. // Purge purges the storage for the given instance.
func (meta *Meta) Purge(instance models.Instance, domain string) error { func (meta *Meta) Purge(ctx context.Context, instance models.Instance, domain string) error {
return meta.Storage(instance.Slug).Purge() return meta.Storage(instance.Slug).Purge(ctx)
} }

View file

@ -1,6 +1,7 @@
package meta package meta
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -24,8 +25,8 @@ type Storage struct {
// Get retrieves metadata with the provided key and deserializes the first one into target. // Get retrieves metadata with the provided key and deserializes the first one into target.
// If no metadatum exists, returns [ErrMetadatumNotSet]. // If no metadatum exists, returns [ErrMetadatumNotSet].
func (s Storage) Get(key Key, target any) error { func (s Storage) Get(ctx context.Context, key Key, target any) error {
table, err := s.sql.QueryTable(true, models.MetadataTable) table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
if err != nil { if err != nil {
return err 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. // The function is intended to return a target for deserialization.
// //
// When no metadatum exists, targets is not called, and nil error is returned. // 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 { func (s Storage) GetAll(ctx context.Context, key Key, target func(index, total int) any) error {
table, err := s.sql.QueryTable(true, models.MetadataTable) table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
if err != nil { if err != nil {
return err 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. // Delete deletes all metadata with the provided key.
func (s Storage) Delete(key Key) error { func (s Storage) Delete(ctx context.Context, key Key) error {
table, err := s.sql.QueryTable(true, models.MetadataTable) table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
if err != nil { if err != nil {
return err return err
} }
@ -96,8 +97,8 @@ func (s Storage) Delete(key Key) error {
// Set serializes value and stores it with the provided key. // Set serializes value and stores it with the provided key.
// Any other metadata with the same key is deleted. // Any other metadata with the same key is deleted.
func (s Storage) Set(key Key, value any) error { func (s Storage) Set(ctx context.Context, key Key, value any) error {
table, err := s.sql.QueryTable(true, models.MetadataTable) table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
if err != nil { if err != nil {
return err 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. // Set serializes values and stores them with the provided key.
// Any other metadata with the same key is deleted. // Any other metadata with the same key is deleted.
func (s Storage) SetAll(key Key, values ...any) error { func (s Storage) SetAll(ctx context.Context, key Key, values ...any) error {
table, err := s.sql.QueryTable(true, models.MetadataTable) table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
if err != nil { if err != nil {
return err return err
} }
@ -165,8 +166,8 @@ func (s Storage) SetAll(key Key, values ...any) error {
} }
// Purge removes all metadata, regardless of key. // Purge removes all metadata, regardless of key.
func (s Storage) Purge() error { func (s Storage) Purge(ctx context.Context) error {
table, err := s.sql.QueryTable(true, models.MetadataTable) table, err := s.sql.QueryTable(ctx, true, models.MetadataTable)
if err != nil { if err != nil {
return err return err
} }
@ -181,22 +182,22 @@ func (s Storage) Purge() error {
// TypedKey represents a convenience wrapper for a given with a given value. // TypedKey represents a convenience wrapper for a given with a given value.
type TypedKey[Value any] Key type TypedKey[Value any] Key
func (f TypedKey[Value]) Get(s *Storage) (value Value, err error) { func (f TypedKey[Value]) Get(ctx context.Context, s *Storage) (value Value, err error) {
err = s.Get(Key(f), &value) err = s.Get(ctx, Key(f), &value)
return return
} }
func (f TypedKey[Value]) GetOrSet(s *Storage, dflt Value) (value Value, err error) { func (f TypedKey[Value]) GetOrSet(ctx context.Context, s *Storage, dflt Value) (value Value, err error) {
value, err = f.Get(s) value, err = f.Get(ctx, s)
if err == ErrMetadatumNotSet { if err == ErrMetadatumNotSet {
value = dflt value = dflt
err = f.Set(s, value) err = f.Set(ctx, s, value)
} }
return return
} }
func (f TypedKey[Value]) GetAll(m *Storage) (values []Value, err error) { func (f TypedKey[Value]) GetAll(ctx context.Context, m *Storage) (values []Value, err error) {
err = m.GetAll(Key(f), func(index, total int) any { err = m.GetAll(ctx, Key(f), func(index, total int) any {
if values == nil { if values == nil {
values = make([]Value, total) values = make([]Value, total)
} }
@ -205,14 +206,14 @@ func (f TypedKey[Value]) GetAll(m *Storage) (values []Value, err error) {
return values, err return values, err
} }
func (f TypedKey[Value]) Set(m *Storage, value Value) error { func (f TypedKey[Value]) Set(ctx context.Context, m *Storage, value Value) error {
return m.Set(Key(f), value) return m.Set(ctx, Key(f), value)
} }
func (f TypedKey[Value]) SetAll(m *Storage, values ...Value) error { func (f TypedKey[Value]) SetAll(ctx context.Context, m *Storage, values ...Value) error {
return m.SetAll(Key(f), collection.AsAny(values)...) return m.SetAll(ctx, Key(f), collection.AsAny(values)...)
} }
func (f TypedKey[Value]) Delete(m *Storage) error { func (f TypedKey[Value]) Delete(ctx context.Context, m *Storage) error {
return m.Delete(Key(f)) return m.Delete(ctx, Key(f))
} }

View file

@ -1,6 +1,8 @@
package component package component
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/models"
) )
@ -10,9 +12,9 @@ type Provisionable interface {
// Provision provisions resources specific to the provided instance. // Provision provisions resources specific to the provided instance.
// Domain holds the full (unique) domain name of the given 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. // Purge purges resources specific to the provided instance.
// Domain holds the full (unique) domain name of the given 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
} }

View file

@ -9,20 +9,34 @@ import (
) )
// updatePrefixes starts updating prefixes // 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() { go func() {
for t := range timex.TickContext(ctx, resolver.RefreshInterval) { for t := range timex.TickContext(ctx, resolver.RefreshInterval) {
io.Printf("[%s]: reloading prefixes\n", t.Format(time.Stamp)) 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. // AllPrefixes returns a list of all prefixes from the server.
// Prefixes may be cached on the server // Prefixes may be cached on the server
func (resolver *Resolver) AllPrefixes() (map[string]string, error) { func (resolver *Resolver) AllPrefixes(ctx context.Context) (map[string]string, error) {
instances, err := resolver.Instances.All() instances, err := resolver.Instances.All(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,7 +51,7 @@ func (resolver *Resolver) AllPrefixes() (map[string]string, error) {
// failed to fetch prefixes for this particular instance // failed to fetch prefixes for this particular instance
// => skip it! // => skip it!
prefixes, err := instance.Prefixes().AllCached() prefixes, err := instance.Prefixes().AllCached(ctx)
if err != nil { if err != nil {
lastErr = err lastErr = err
continue continue

View file

@ -28,7 +28,7 @@ type Resolver struct {
func (resolver *Resolver) Routes() []string { return []string{"/go/", "/wisski/get/"} } 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 var err error
return resolver.handler.Get(func() (p wdresolve.ResolveHandler) { return resolver.handler.Get(func() (p wdresolve.ResolveHandler) {
p.TrustXForwardedProto = true p.TrustXForwardedProto = true
@ -51,7 +51,7 @@ func (resolver *Resolver) Handler(route string, context context.Context, io stre
} }
// start updating prefixes // start updating prefixes
resolver.updatePrefixes(io, context) resolver.updatePrefixes(ctx, io)
// resolve the prefixes // resolve the prefixes
p.Resolver = resolvers.InOrder{ p.Resolver = resolvers.InOrder{

View file

@ -15,5 +15,5 @@ type Servable interface {
Routes() []string Routes() []string
// Handler returns the handler for the requested route // 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)
} }

View file

@ -1,7 +1,6 @@
package solr package solr
import ( import (
"context"
"embed" "embed"
"path/filepath" "path/filepath"
"time" "time"
@ -15,8 +14,7 @@ type Solr struct {
BaseURL string // upstream solr url 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 { func (s *Solr) Path() string {

View file

@ -1,6 +1,7 @@
package sql package sql
import ( import (
"context"
"errors" "errors"
"io" "io"
@ -14,10 +15,10 @@ func (*SQL) BackupName() string {
} }
// Backup makes a backup of all SQL databases into the path dest. // Backup makes a backup of all SQL databases into the path dest.
func (sql *SQL) Backup(context component.StagingContext) error { func (sql *SQL) Backup(scontext component.StagingContext) error {
return context.AddFile("", func(file io.Writer) error { return scontext.AddFile("", func(ctx context.Context, file io.Writer) error {
io := context.IO().Streams(file, nil, nil, 0).NonInteractive() io := scontext.IO().Streams(file, nil, nil, 0).NonInteractive()
code, err := sql.Stack(sql.Environment).Exec(io, "sql", "mysqldump", "--all-databases") code, err := sql.Stack(sql.Environment).Exec(ctx, io, "sql", "mysqldump", "--all-databases")
if err != nil { if err != nil {
return err return err
} }

View file

@ -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 ========== // ========== connection via gorm ==========
// //
// QueryTable returns a gorm.DB to connect to the provided distillery database table // 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) conn, err := sql.connect(sql.Config.DistilleryDatabase)
if err != nil { if err != nil {
return nil, err return nil, err
@ -79,7 +71,7 @@ func (sql *SQL) QueryTable(silent bool, table string) (*gorm.DB, error) {
} }
// set the table // set the table
db = db.Table(table) db = db.WithContext(ctx).Table(table)
// check that nothing went wrong // check that nothing went wrong
if db.Error != nil { 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 // 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! // TODO: Establish a convention on when to wait for this!
return timex.TickUntilFunc(func(time.Time) bool { return timex.TickUntilFunc(func(time.Time) bool {
_, err := sql.QueryTable(true, models.InstanceTable) _, err := sql.QueryTable(ctx, true, models.InstanceTable)
return err == nil return err == nil
}, sql.PollContext, sql.PollInterval) }, ctx, sql.PollInterval)
} }
// //

View file

@ -1,6 +1,7 @@
package sql package sql
import ( import (
"context"
"errors" "errors"
"github.com/FAU-CDI/wisski-distillery/internal/models" "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") var errProvisionInvalidGrant = errors.New("Provision: Grant failed")
// Provision provisions sql-specific resource for the given instance // Provision provisions sql-specific resource for the given instance
func (sql *SQL) Provision(instance models.Instance, domain string) error { func (sql *SQL) Provision(ctx context.Context, instance models.Instance, domain string) error {
return sql.CreateDatabase(instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword) return sql.CreateDatabase(ctx, instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword)
} }
// Purge purges sql-specific resources for the given instance // 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( return errorx.First(
sql.PurgeDatabase(instance.SqlDatabase), 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. // 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. // 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. // 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. // 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. // 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. // 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 return err
} }
query := "CREATE DATABASE `" + name + "`;" + query := "CREATE DATABASE `" + name + "`;" +
"CREATE USER '" + user + "'@'%' IDENTIFIED BY '" + password + "';" + "CREATE USER '" + user + "'@'%' IDENTIFIED BY '" + password + "';" +
"GRANT ALL PRIVILEGES ON `" + name + "`.* TO `" + user + "`@`%`; FLUSH PRIVILEGES;" "GRANT ALL PRIVILEGES ON `" + name + "`.* TO `" + user + "`@`%`; FLUSH PRIVILEGES;"
if !sql.unsafeQueryShell(query) { if !sql.unsafeQueryShell(ctx, query) {
return errProvisionInvalidGrant return errProvisionInvalidGrant
} }
@ -63,7 +64,7 @@ var errCreateSuperuserGrant = errors.New("CreateSuperUser: Grant failed")
// It then grants this user superuser status in the database. // It then grants this user superuser status in the database.
// //
// CreateSuperuser internally waits for the database to become available. // 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. // NOTE(twiesing): This function unsafely uses the shell directly to create a superuser.
// This is for two reasons: // This is for two reasons:
// (1) this is used during bootstraping // (1) this is used during bootstraping
@ -74,7 +75,7 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error
return errProvisionInvalidDatabaseParams return errProvisionInvalidDatabaseParams
} }
if err := sql.unsafeWaitShell(); err != nil { if err := sql.unsafeWaitShell(ctx); err != nil {
return err return err
} }
@ -85,7 +86,7 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error
query := "CREATE USER " + IfNotExists + " '" + user + "'@'%' IDENTIFIED BY '" + password + "';" + query := "CREATE USER " + IfNotExists + " '" + user + "'@'%' IDENTIFIED BY '" + password + "';" +
"GRANT ALL PRIVILEGES ON *.* TO '" + user + "'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" "GRANT ALL PRIVILEGES ON *.* TO '" + user + "'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;"
if !sql.unsafeQueryShell(query) { if !sql.unsafeQueryShell(ctx, query) {
return errCreateSuperuserGrant 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") var errPurgeUser = errors.New("PurgeUser: Failed to drop user")
// SQLPurgeUser deletes the specified user from the database // 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) { if !sqle.IsSafeDatabaseSingleQuote(user) {
return errPurgeUser return errPurgeUser
} }
query := "DROP USER IF EXISTS '" + user + "'@'%';" + query := "DROP USER IF EXISTS '" + user + "'@'%';" +
"FLUSH PRIVILEGES;" "FLUSH PRIVILEGES;"
if !sql.unsafeQueryShell(query) { if !sql.unsafeQueryShell(ctx, query) {
return errPurgeUser return errPurgeUser
} }

View file

@ -1,6 +1,7 @@
package sql package sql
import ( import (
"context"
"io" "io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "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) SnapshotName() string { return "sql" }
func (sql *SQL) Snapshot(wisski models.Instance, context component.StagingContext) error { func (sql *SQL) Snapshot(wisski models.Instance, scontext component.StagingContext) error {
return context.AddDirectory(".", func() error { return scontext.AddDirectory(".", func(ctx context.Context) error {
return context.AddFile(wisski.SqlDatabase+".sql", func(file io.Writer) error { return scontext.AddFile(wisski.SqlDatabase+".sql", func(ctx context.Context, file io.Writer) error {
return sql.SnapshotDB(context.IO(), file, wisski.SqlDatabase) return sql.SnapshotDB(ctx, scontext.IO(), file, wisski.SqlDatabase)
}) })
}) })
} }
// SnapshotDB makes a backup of the sql database into dest. // 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() 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 { if err != nil {
return err return err
} }

View file

@ -1,7 +1,6 @@
package sql package sql
import ( import (
"context"
"embed" "embed"
"path/filepath" "path/filepath"
"time" "time"
@ -16,8 +15,7 @@ type SQL struct {
ServerURL string // upstream server url 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] lazyNetwork lazy.Lazy[string]
} }

View file

@ -1,6 +1,7 @@
package sql package sql
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@ -16,22 +17,22 @@ import (
// Shell runs a mysql shell with the provided databases. // 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. // 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) { func (sql *SQL) Shell(ctx context.Context, io stream.IOStream, argv ...string) (int, error) {
return sql.Stack(sql.Environment).Exec(io, "sql", "mysql", argv...) return sql.Stack(sql.Environment).Exec(ctx, io, "sql", "mysql", argv...)
} }
// unsafeWaitShell waits for a connection via the database shell to succeed // 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() n := stream.FromNil()
return timex.TickUntilFunc(func(time.Time) bool { 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 return err == nil && code == 0
}, sql.PollContext, sql.PollInterval) }, ctx, sql.PollInterval)
} }
// unsafeQuery shell executes a raw database query. // unsafeQuery shell executes a raw database query.
func (sql *SQL) unsafeQueryShell(query string) bool { func (sql *SQL) unsafeQueryShell(ctx context.Context, query string) bool {
code, err := sql.Shell(stream.FromNil(), "-e", query) code, err := sql.Shell(ctx, stream.FromNil(), "-e", query)
return err == nil && code == 0 return err == nil && code == 0
} }
@ -43,18 +44,18 @@ var errSQLUnableToMigrate = exit.Error{
} }
// Update initializes or updates the SQL database. // 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! // unsafely create the admin user!
{ {
if err := sql.unsafeWaitShell(); err != nil { if err := sql.unsafeWaitShell(ctx); err != nil {
return err return err
} }
logging.LogMessage(io, "Creating administrative user") logging.LogMessage(io, "Creating administrative user")
{ {
username := sql.Config.MysqlAdminUser username := sql.Config.MysqlAdminUser
password := sql.Config.MysqlAdminPassword 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 return errSQLUnableToCreateUser
} }
} }
@ -74,7 +75,7 @@ func (sql *SQL) Update(io stream.IOStream) error {
// wait for the database to come up // wait for the database to come up
logging.LogMessage(io, "Waiting for database update to be complete") logging.LogMessage(io, "Waiting for database update to be complete")
sql.WaitQueryTable() sql.WaitQueryTable(ctx)
tables := []struct { tables := []struct {
name string name string
@ -107,7 +108,7 @@ func (sql *SQL) Update(io stream.IOStream) error {
return logging.LogOperation(func() error { return logging.LogOperation(func() error {
for _, table := range tables { for _, table := range tables {
logging.LogMessage(io, "migrating %q table", table.name) 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 { if err != nil {
return errSQLUnableToMigrate.WithMessageF(table.name, "unable to access table") return errSQLUnableToMigrate.WithMessageF(table.name, "unable to access table")
} }

View file

@ -65,7 +65,7 @@ func (ssh2 *SSH2) handleAuth(ctx ssh.Context, key ssh.PublicKey) bool {
// grab permissions for each instance // grab permissions for each instance
{ {
instances, err := ssh2.Instances.All() instances, err := ssh2.Instances.All(ctx)
if err != nil { if err != nil {
return false return false
} }

View file

@ -4,6 +4,7 @@ package component
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"io/fs" "io/fs"
"path/filepath" "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. // This does not have a direct 'docker compose' shell equivalent.
// //
// See also Up. // 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 { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -54,7 +55,7 @@ func (ds Stack) Update(io stream.IOStream, start bool) error {
} }
} }
if start { if start {
return ds.Up(io) return ds.Up(ctx, io)
} }
return nil 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. // Up creates and starts the containers in this Stack.
// It is equivalent to 'docker compose up --remove-orphans --detach' on the shell. // It is equivalent to 'docker compose up --remove-orphans --detach' on the shell.
func (ds Stack) Up(io stream.IOStream) error { func (ds Stack) Up(ctx context.Context, io stream.IOStream) error {
code, err := ds.compose(io, "up", "--remove-orphans", "--detach") code, err := ds.compose(ctx, io, "up", "--remove-orphans", "--detach")
if err != nil { if err != nil {
return err 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 is equivalent to 'docker compose exec $service $executable $args...'.
// //
// It returns the exit code of the process. // 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"} compose := []string{"exec"}
if io.StdinIsATerminal() { if io.StdinIsATerminal() {
compose = append(compose, "-ti") 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, service)
compose = append(compose, executable) compose = append(compose, executable)
compose = append(compose, args...) 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. // Run runs a command in a running container with the given executable.
// It is equivalent to 'docker compose run [--rm] $service $executable $args...'. // It is equivalent to 'docker compose run [--rm] $service $executable $args...'.
// //
// It returns the exit code of the process. // 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"} compose := []string{"run"}
if autoRemove { if autoRemove {
compose = append(compose, "--rm") 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, service, command)
compose = append(compose, args...) compose = append(compose, args...)
code, err := ds.compose(io, compose...) code, err := ds.compose(ctx, io, compose...)
if err != nil { if err != nil {
return environment.ExecCommandError, 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. // Restart restarts all containers in this Stack.
// It is equivalent to 'docker compose restart' on the shell. // It is equivalent to 'docker compose restart' on the shell.
func (ds Stack) Restart(io stream.IOStream) error { func (ds Stack) Restart(ctx context.Context, io stream.IOStream) error {
code, err := ds.compose(io, "restart") code, err := ds.compose(ctx, io, "restart")
if err != nil { if err != nil {
return err 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") var errStackPs = errors.New("Stack.Ps: Down returned non-zero exit code")
// Ps returns the ids of the containers currently running // 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 // create a buffer
var buffer bytes.Buffer var buffer bytes.Buffer
// read the ids from the command! // 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 { if err != nil {
return nil, err 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. // Down stops and removes all containers in this Stack.
// It is equivalent to 'docker compose down -v' on the shell. // It is equivalent to 'docker compose down -v' on the shell.
func (ds Stack) Down(io stream.IOStream) error { func (ds Stack) Down(ctx context.Context, io stream.IOStream) error {
code, err := ds.compose(io, "down", "-v") code, err := ds.compose(ctx, io, "down", "-v")
if err != nil { if err != nil {
return err 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. // NOTE(twiesing): Check if this can be replaced by an internal call to libcompose.
// But probably not. // 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 == "" { if ds.DockerExecutable == "" {
var err error var err error
ds.DockerExecutable, err = ds.Env.LookPathAbs("docker") 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 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. // 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. // Installation is non-interactive, but will provide debugging output onto io.
// InstallationContext // 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 env := is.Stack.Env
if is.ContextPath != "" { if is.ContextPath != "" {
// setup the base files // setup the base files
@ -277,7 +278,7 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon
// copy over file from context // copy over file from context
io.Printf("[copy] %s (from %s)\n", dst, src) 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) return errors.Wrapf(err, "Unable to copy file %s", src)
} }
} }

View file

@ -1,6 +1,7 @@
package triplestore package triplestore
import ( import (
"context"
"encoding/json" "encoding/json"
"io" "io"
@ -10,19 +11,17 @@ import (
func (ts *Triplestore) BackupName() string { return "triplestore" } func (ts *Triplestore) BackupName() string { return "triplestore" }
// Backup makes a backup of all Triplestore repositories databases into the path dest. // 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 { for _, repo := range repos {
if err := context.AddFile(repo.ID+".nq", func(file io.Writer) error { if err := scontext.AddFile(repo.ID+".nq", func(ctx context.Context, file io.Writer) error {
_, err := ts.SnapshotDB(file, repo.ID) _, err := ts.SnapshotDB(ctx, file, repo.ID)
return err return err
}); err != nil { }); err != nil {
return err return err
@ -32,8 +31,8 @@ func (ts *Triplestore) Backup(context component.StagingContext) error {
}) })
} }
func (ts Triplestore) listRepositories() (repos []Repository, err error) { func (ts Triplestore) listRepositories(ctx context.Context) (repos []Repository, err error) {
res, err := ts.OpenRaw("GET", "/rest/repositories", nil, "", "application/json") res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "application/json")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -2,6 +2,7 @@ package triplestore
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"io" "io"
"mime/multipart" "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 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 // 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 reader io.Reader
var contentType string var contentType string
@ -66,7 +67,7 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str
DisableKeepAlives: true, DisableKeepAlives: true,
}, },
} }
req, err := http.NewRequest(method, ts.BaseURL+url, reader) req, err := http.NewRequestWithContext(ctx, method, ts.BaseURL+url, reader)
if err != nil { if err != nil {
return nil, err 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. // Wait waits for the connection to the Triplestore to succeed.
// This is achieved using a polling strategy. // This is achieved using a polling strategy.
func (ts Triplestore) Wait() error { func (ts Triplestore) Wait(ctx context.Context) error {
n := stream.FromNil() n := stream.FromNil()
return timex.TickUntilFunc(func(time.Time) bool { 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) n.EPrintf("[Triplestore.Wait]: %s\n", err)
if err != nil { if err != nil {
return false return false
} }
defer res.Body.Close() defer res.Body.Close()
return true return true
}, ts.PollContext, ts.PollInterval) }, ctx, ts.PollInterval)
} }
// PurgeUser deletes the specified user from the triplestore. // PurgeUser deletes the specified user from the triplestore.
// When the user does not exist, returns no error. // When the user does not exist, returns no error.
func (ts Triplestore) PurgeUser(user string) error { func (ts Triplestore) PurgeUser(ctx context.Context, user string) error {
res, err := ts.OpenRaw("DELETE", "/rest/security/users/"+user, nil, "", "") res, err := ts.OpenRaw(ctx, "DELETE", "/rest/security/users/"+user, nil, "", "")
if err != nil { if err != nil {
return err return err
} }
@ -114,8 +115,8 @@ func (ts Triplestore) PurgeUser(user string) error {
// PurgeRepo deletes the specified repo from the triplestore. // PurgeRepo deletes the specified repo from the triplestore.
// When the repo does not exist, returns no error. // When the repo does not exist, returns no error.
func (ts Triplestore) PurgeRepo(repo string) error { func (ts Triplestore) PurgeRepo(ctx context.Context, repo string) error {
res, err := ts.OpenRaw("DELETE", "/rest/repositories/"+repo, nil, "", "") res, err := ts.OpenRaw(ctx, "DELETE", "/rest/repositories/"+repo, nil, "", "")
if err != nil { if err != nil {
return err return err
} }

View file

@ -2,6 +2,7 @@ package triplestore
import ( import (
"bytes" "bytes"
"context"
"net/http" "net/http"
_ "embed" _ "embed"
@ -20,19 +21,19 @@ var errTripleStoreFailedRepository = exit.Error{
//go:embed create-repo.ttl //go:embed create-repo.ttl
var createRepoTTL []byte var createRepoTTL []byte
func (ts *Triplestore) Provision(instance models.Instance, domain string) error { func (ts *Triplestore) Provision(ctx context.Context, instance models.Instance, domain string) error {
return ts.CreateRepository(instance.GraphDBRepository, domain, instance.GraphDBUsername, instance.GraphDBPassword) 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( return errorx.First(
ts.PurgeRepo(instance.GraphDBRepository), ts.PurgeRepo(ctx, instance.GraphDBRepository),
ts.PurgeUser(instance.GraphDBUsername), ts.PurgeUser(ctx, instance.GraphDBUsername),
) )
} }
func (ts *Triplestore) CreateRepository(name, domain, user, password string) error { func (ts *Triplestore) CreateRepository(ctx context.Context, name, domain, user, password string) error {
if err := ts.Wait(); err != nil { if err := ts.Wait(ctx); err != nil {
return err return err
} }
@ -48,7 +49,7 @@ func (ts *Triplestore) CreateRepository(name, domain, user, password string) err
// do the create! // 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 { if err != nil {
return errTripleStoreFailedRepository.WithMessageF(err) 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 // 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, Password: password,
AppSettings: TriplestoreUserAppSettings{ AppSettings: TriplestoreUserAppSettings{
DefaultInference: true, DefaultInference: true,

View file

@ -1,6 +1,7 @@
package triplestore package triplestore
import ( import (
"context"
"io" "io"
"net/http" "net/http"
@ -13,10 +14,10 @@ func (Triplestore) SnapshotNeedsRunning() bool { return false }
func (Triplestore) SnapshotName() string { return "triplestore" } func (Triplestore) SnapshotName() string { return "triplestore" }
func (ts *Triplestore) Snapshot(wisski models.Instance, context component.StagingContext) error { func (ts *Triplestore) Snapshot(wisski models.Instance, scontext component.StagingContext) error {
return context.AddDirectory(".", func() error { return scontext.AddDirectory(".", func(ctx context.Context) error {
return context.AddFile(wisski.GraphDBRepository+".nq", func(file io.Writer) error { return scontext.AddFile(wisski.GraphDBRepository+".nq", func(ctx context.Context, file io.Writer) error {
_, err := ts.SnapshotDB(file, wisski.GraphDBRepository) _, err := ts.SnapshotDB(ctx, file, wisski.GraphDBRepository)
return err 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") var errTSBackupWrongStatusCode = errors.New("Triplestore.Backup: Wrong status code")
// SnapshotDB snapshots the provided repository into dst // SnapshotDB snapshots the provided repository into dst
func (ts Triplestore) SnapshotDB(dst io.Writer, repo string) (int64, error) { func (ts Triplestore) SnapshotDB(ctx context.Context, dst io.Writer, repo string) (int64, error) {
res, err := ts.OpenRaw("GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads") res, err := ts.OpenRaw(ctx, "GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads")
if err != nil { if err != nil {
return 0, err return 0, err
} }

View file

@ -1,7 +1,6 @@
package triplestore package triplestore
import ( import (
"context"
"embed" "embed"
"path/filepath" "path/filepath"
"time" "time"
@ -15,8 +14,7 @@ type Triplestore struct {
BaseURL string // upstream server url 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 { func (ts *Triplestore) Path() string {

View file

@ -1,6 +1,7 @@
package triplestore package triplestore
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
@ -11,15 +12,15 @@ import (
var errTriplestoreFailedSecurity = errors.New("failed to enable triplestore security: request did not succeed with HTTP 200 OK") 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") logging.LogMessage(io, "Waiting for Triplestore")
if err := ts.Wait(); err != nil { if err := ts.Wait(ctx); err != nil {
return err return err
} }
logging.LogMessage(io, "Resetting admin user password") 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, Password: ts.Config.TriplestoreAdminPassword,
AppSettings: TriplestoreUserAppSettings{ AppSettings: TriplestoreUserAppSettings{
DefaultInference: true, DefaultInference: true,
@ -51,7 +52,7 @@ func (ts Triplestore) Update(io stream.IOStream) error {
logging.LogMessage(io, "Enabling Triplestore security") 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 { if err != nil {
return fmt.Errorf("failed to enable triplestore security: %s", err) return fmt.Errorf("failed to enable triplestore security: %s", err)
} }

View file

@ -2,7 +2,7 @@
package dis package dis
import ( import (
"context" "io"
"sync" "sync"
"time" "time"
@ -32,8 +32,8 @@ type Distillery struct {
// core holds the core of the distillery // core holds the core of the distillery
component.Still component.Still
// internal context for the distillery // Where interactive progress is displayed
context context.Context Progress io.Writer
// Upstream holds information to connect to the various running // Upstream holds information to connect to the various running
// distillery components. // distillery components.
@ -54,11 +54,6 @@ type Upstream struct {
Solr string Solr string
} }
// Context returns a new Context belonging to this distillery
func (dis *Distillery) Context() context.Context {
return dis.context
}
// //
// PUBLIC COMPONENT GETTERS // PUBLIC COMPONENT GETTERS
// //
@ -110,17 +105,14 @@ func (dis *Distillery) allComponents() []initFunc {
manual(func(ts *triplestore.Triplestore) { manual(func(ts *triplestore.Triplestore) {
ts.BaseURL = "http://" + dis.Upstream.Triplestore ts.BaseURL = "http://" + dis.Upstream.Triplestore
ts.PollContext = dis.Context()
ts.PollInterval = time.Second ts.PollInterval = time.Second
}), }),
manual(func(sql *sql.SQL) { manual(func(sql *sql.SQL) {
sql.ServerURL = dis.Upstream.SQL sql.ServerURL = dis.Upstream.SQL
sql.PollContext = dis.Context()
sql.PollInterval = time.Second sql.PollInterval = time.Second
}), }),
manual(func(s *solr.Solr) { manual(func(s *solr.Solr) {
s.BaseURL = dis.Upstream.Solr s.BaseURL = dis.Upstream.Solr
s.PollContext = dis.Context()
s.PollInterval = time.Second s.PollInterval = time.Second
}), }),

View file

@ -21,7 +21,6 @@ var errOpenConfig = exit.Error{
// NewDistillery creates a new distillery from the provided flags // NewDistillery creates a new distillery from the provided flags
func NewDistillery(params cli.Params, flags cli.Flags, req cli.Requirements) (dis *Distillery, err error) { func NewDistillery(params cli.Params, flags cli.Flags, req cli.Requirements) (dis *Distillery, err error) {
dis = &Distillery{ dis = &Distillery{
context: params.Context,
Still: component.Still{ Still: component.Still{
Environment: new(environment.Native), Environment: new(environment.Native),
}, },

View file

@ -1,18 +1,22 @@
// Package phpx provides functionalities for interacting with PHP code // Package phpx provides functionalities for interacting with PHP code
package phpx package phpx
import "github.com/tkw1536/goprogram/stream" import (
"context"
"github.com/tkw1536/goprogram/stream"
)
// Executor represents anything that can spawn // Executor represents anything that can spawn
type Executor interface { type Executor interface {
// Spawn spawns a new (independent) process executing code. // Spawn spawns a new (independent) process executing code.
// It should return only once the execution terminates. // 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 // 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 { func (sf SpawnFunc) Spawn(ctx context.Context, str stream.IOStream, code string) error {
return sf(str, code) return sf(ctx, str, code)
} }

View file

@ -10,6 +10,7 @@ import (
_ "embed" _ "embed"
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy" "github.com/FAU-CDI/wisski-distillery/pkg/lazy"
"github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/lib/collection"
"github.com/tkw1536/goprogram/lib/nobufio" "github.com/tkw1536/goprogram/lib/nobufio"
@ -21,6 +22,9 @@ import (
// //
// A server, once used, should be closed using the [Close] method. // A server, once used, should be closed using the [Close] method.
type Server struct { type Server struct {
// Context to use for the server
Context context.Context
// Executor is the executor used by this server. // Executor is the executor used by this server.
// It may not be modified concurrently with other processes. // It may not be modified concurrently with other processes.
Executor Executor Executor Executor
@ -35,8 +39,8 @@ type Server struct {
m sync.Mutex // prevents concurrent access on any of the methods 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 { func (server *Server) prepare() error {
@ -57,7 +61,8 @@ func (server *Server) prepare() error {
} }
// create a context to close the server // 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 // start the shell process, which will close everything once done
go func() { go func() {
@ -67,12 +72,12 @@ func (server *Server) prepare() error {
or.Close() or.Close()
ow.Close() ow.Close()
cancel() server.cancel()
}() }()
// start the server // start the server
io := stream.NewIOStream(ow, nil, ir, 0) 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}) 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. // 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. // 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 { if err := server.prepare(); err != nil {
return err return err
} }
@ -111,8 +116,11 @@ func (server *Server) MarshalEval(value any, code string) error {
// find a delimiter for the code, and then send // find a delimiter for the code, and then send
io.WriteString(server.in, input) io.WriteString(server.in, input)
// read the next line (as a response) data, err, _ := cancel.WithContext2(ctx, func(start func()) (string, error) {
data, err := nobufio.ReadLine(server.out) return nobufio.ReadLine(server.out)
}, func() {
server.cancel()
})
if err != nil { if err != nil {
return ServerError{Message: errReceive, Err: err} 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 // Eval is like [MarshalEval], but returns the value as an any
func (server *Server) Eval(code string) (value any, err error) { func (server *Server) Eval(ctx context.Context, code string) (value any, err error) {
err = server.MarshalEval(&value, code) err = server.MarshalEval(ctx, &value, code)
return 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. // Arguments are sent to php using json Marshal, and are 'json_decode'd on the php side.
// //
// Return values are received as in [MarshalEval]. // 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 of function to call
name := MarshalString(function) name := MarshalString(function)
@ -172,12 +180,12 @@ func (server *Server) MarshalCall(value any, function string, args ...any) error
} }
// and evaluate the code // 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 // 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) { func (server *Server) Call(ctx context.Context, function string, args ...any) (value any, err error) {
err = server.MarshalCall(&value, function, args...) err = server.MarshalCall(ctx, &value, function, args...)
return return
} }

View file

@ -1,6 +1,7 @@
package barrel package barrel
import ( import (
"context"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
@ -15,40 +16,40 @@ import (
// Build builds or rebuilds the barel connected to this instance. // Build builds or rebuilds the barel connected to this instance.
// //
// It also logs the current time into the metadata belonging 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 { func (barrel *Barrel) Build(ctx context.Context, stream stream.IOStream, start bool) error {
if !barrel.Locker.TryLock() { if !barrel.Locker.TryLock(ctx) {
err := locker.Locked err := locker.Locked
return err return err
} }
defer barrel.Locker.Unlock() defer barrel.Locker.Unlock(ctx)
stack := barrel.Stack() stack := barrel.Stack()
var context component.InstallationContext var context component.InstallationContext
{ {
err := stack.Install(stream, context) err := stack.Install(ctx, stream, context)
if err != nil { if err != nil {
return err return err
} }
} }
{ {
err := stack.Update(stream, start) err := stack.Update(ctx, stream, start)
if err != nil { if err != nil {
return err return err
} }
} }
// store the current last rebuild // store the current last rebuild
return barrel.setLastRebuild() return barrel.setLastRebuild(ctx)
} }
// TODO: Move this to time.Time // TODO: Move this to time.Time
var lastRebuild = mstore.For[int64]("lastRebuild") var lastRebuild = mstore.For[int64]("lastRebuild")
func (barrel Barrel) LastRebuild() (t time.Time, err error) { func (barrel Barrel) LastRebuild(ctx context.Context) (t time.Time, err error) {
epoch, err := lastRebuild.Get(barrel.MStore) epoch, err := lastRebuild.Get(ctx, barrel.MStore)
if err == meta.ErrMetadatumNotSet { if err == meta.ErrMetadatumNotSet {
return t, nil return t, nil
} }
@ -60,8 +61,8 @@ func (barrel Barrel) LastRebuild() (t time.Time, err error) {
return time.Unix(epoch, 0), nil return time.Unix(epoch, 0), nil
} }
func (barrel *Barrel) setLastRebuild() error { func (barrel *Barrel) setLastRebuild(ctx context.Context) error {
return lastRebuild.Set(barrel.MStore, time.Now().Unix()) return lastRebuild.Set(ctx, barrel.MStore, time.Now().Unix())
} }
type LastRebuildFetcher struct { type LastRebuildFetcher struct {
@ -70,7 +71,7 @@ type LastRebuildFetcher struct {
Barrel *Barrel Barrel *Barrel
} }
func (lbr *LastRebuildFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (lbr *LastRebuildFetcher) Fetch(ctx context.Context, flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.LastRebuild, _ = lbr.Barrel.LastRebuild() info.LastRebuild, _ = lbr.Barrel.LastRebuild(ctx)
return return
} }

View file

@ -1,6 +1,7 @@
package drush package drush
import ( import (
"context"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/phpx"
@ -15,8 +16,8 @@ var errCronFailed = exit.Error{
ExitCode: exit.ExitGeneric, ExitCode: exit.ExitGeneric,
} }
func (drush *Drush) Cron(io stream.IOStream) error { func (drush *Drush) Cron(ctx context.Context, io stream.IOStream) error {
code, err := drush.Barrel.Shell(io, "/runtime/cron.sh") code, err := drush.Barrel.Shell(ctx, io, "/runtime/cron.sh")
if err != nil { if err != nil {
io.EPrintln(err) io.EPrintln(err)
} }
@ -29,9 +30,9 @@ func (drush *Drush) Cron(io stream.IOStream) error {
return nil 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 var timestamp int64
err = drush.PHP.EvalCode(server, &timestamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `) err = drush.PHP.EvalCode(ctx, server, &timestamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `)
if err != nil { if err != nil {
return return
} }
@ -49,6 +50,6 @@ func (lbr *LastCronFetcher) Fetch(flags ingredient.FetcherFlags, info *status.Wi
return return
} }
info.LastRebuild, _ = lbr.Drush.LastCron(flags.Server) info.LastRebuild, _ = lbr.Drush.LastCron(flags.Context, flags.Server)
return return
} }

View file

@ -1,6 +1,7 @@
package drush package drush
import ( import (
"context"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
@ -18,8 +19,8 @@ var errBlindUpdateFailed = exit.Error{
} }
// Update performs a blind drush update // Update performs a blind drush update
func (drush *Drush) Update(io stream.IOStream) error { func (drush *Drush) Update(ctx context.Context, io stream.IOStream) error {
code, err := drush.Barrel.Shell(io, "/runtime/blind_update.sh") code, err := drush.Barrel.Shell(ctx, io, "/runtime/blind_update.sh")
if err != nil { if err != nil {
return errBlindUpdateFailed.WithMessageF(drush.Slug, environment.ExecCommandError) 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 errBlindUpdateFailed.WithMessageF(drush.Slug, code)
} }
return drush.setLastUpdate() return drush.setLastUpdate(ctx)
} }
const lastUpdate = mstore.For[int64]("lastUpdate") const lastUpdate = mstore.For[int64]("lastUpdate")
func (drush *Drush) LastUpdate() (t time.Time, err error) { func (drush *Drush) LastUpdate(ctx context.Context) (t time.Time, err error) {
epoch, err := lastUpdate.Get(drush.MStore) epoch, err := lastUpdate.Get(ctx, drush.MStore)
if err == meta.ErrMetadatumNotSet { if err == meta.ErrMetadatumNotSet {
return t, nil return t, nil
} }
@ -45,8 +46,8 @@ func (drush *Drush) LastUpdate() (t time.Time, err error) {
return time.Unix(epoch, 0), nil return time.Unix(epoch, 0), nil
} }
func (drush *Drush) setLastUpdate() error { func (drush *Drush) setLastUpdate(ctx context.Context) error {
return lastUpdate.Set(drush.MStore, time.Now().Unix()) return lastUpdate.Set(ctx, drush.MStore, time.Now().Unix())
} }
type LastUpdateFetcher struct { type LastUpdateFetcher struct {
@ -56,6 +57,6 @@ type LastUpdateFetcher struct {
} }
func (lbr *LastUpdateFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { 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 return
} }

View file

@ -1,6 +1,7 @@
package provisioner package provisioner
import ( import (
"context"
"errors" "errors"
"strings" "strings"
@ -19,10 +20,10 @@ type Provisioner struct {
} }
// Provision provisions an instance, assuming that the required databases already exist. // 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 // build the container
if err := provision.Barrel.Build(io, false); err != nil { if err := provision.Barrel.Build(ctx, io, false); err != nil {
return err return err
} }
@ -53,7 +54,7 @@ func (provision *Provisioner) Provision(io stream.IOStream) error {
// TODO: Move the provision script into the control plane! // TODO: Move the provision script into the control plane!
provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ") 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 { if err != nil {
return err return err
} }

View file

@ -1,14 +1,16 @@
package barrel package barrel
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/status"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
// Running checks if this WissKI is currently running. // Running checks if this WissKI is currently running.
func (barrel *Barrel) Running() (bool, error) { func (barrel *Barrel) Running(ctx context.Context) (bool, error) {
ps, err := barrel.Stack().Ps(stream.FromNil()) ps, err := barrel.Stack().Ps(ctx, stream.FromNil())
if err != nil { if err != nil {
return false, err return false, err
} }
@ -22,6 +24,6 @@ type RunningFetcher struct {
} }
func (rf *RunningFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { 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 return
} }

View file

@ -1,8 +1,12 @@
package barrel package barrel
import "github.com/tkw1536/goprogram/stream" import (
"context"
"github.com/tkw1536/goprogram/stream"
)
// Shell executes a shell command inside the instance. // Shell executes a shell command inside the instance.
func (barrel *Barrel) Shell(io stream.IOStream, argv ...string) (int, error) { func (barrel *Barrel) Shell(ctx context.Context, io stream.IOStream, argv ...string) (int, error) {
return barrel.Stack().Exec(io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...) return barrel.Stack().Exec(ctx, io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...)
} }

View file

@ -1,6 +1,8 @@
package bookkeeping package bookkeeping
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
) )
@ -11,8 +13,8 @@ type Bookkeeping struct {
} }
// Save saves this instance in the bookkeeping table // Save saves this instance in the bookkeeping table
func (bk *Bookkeeping) Save() error { func (bk *Bookkeeping) Save(ctx context.Context) error {
sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable) sdb, err := bk.Malt.SQL.QueryTable(ctx, false, models.InstanceTable)
if err != nil { if err != nil {
return err return err
} }
@ -27,8 +29,8 @@ func (bk *Bookkeeping) Save() error {
} }
// Delete deletes this instance from the bookkeeping table // Delete deletes this instance from the bookkeeping table
func (bk *Bookkeeping) Delete() error { func (bk *Bookkeeping) Delete(ctx context.Context) error {
sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable) sdb, err := bk.Malt.SQL.QueryTable(ctx, false, models.InstanceTable)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,6 +1,8 @@
package ingredient package ingredient
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/phpx"
"github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/status"
) )
@ -15,6 +17,7 @@ type WissKIFetcher interface {
// FetcherFlags describes options for a WissKIFetcher // FetcherFlags describes options for a WissKIFetcher
type FetcherFlags struct { type FetcherFlags struct {
Quick bool Context context.Context
Server *phpx.Server Quick bool
Server *phpx.Server
} }

View file

@ -1,6 +1,7 @@
package info package info
import ( import (
"context"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/status"
@ -21,10 +22,11 @@ type Info struct {
// Information fetches information about this WissKI. // Information fetches information about this WissKI.
// TODO: Rework this to be able to determine what kind of information is available. // 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 // setup flags
flags := ingredient.FetcherFlags{ flags := ingredient.FetcherFlags{
Quick: quick, Quick: quick,
Context: ctx,
} }
// potentially setup a new server // potentially setup a new server

View file

@ -16,6 +16,6 @@ func (lbr *SnapshotsFetcher) Fetch(flags ingredient.FetcherFlags, info *status.W
return return
} }
info.Snapshots, _ = lbr.Snapshots() info.Snapshots, _ = lbr.Snapshots(flags.Context)
return return
} }

View file

@ -1,6 +1,8 @@
package locker package locker
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
@ -17,8 +19,8 @@ var Locked = exit.Error{
} }
// TryLock attemps to lock this WissKI and returns if it suceeded // TryLock attemps to lock this WissKI and returns if it suceeded
func (lock *Locker) TryLock() bool { func (lock *Locker) TryLock(ctx context.Context) bool {
table, err := lock.Malt.SQL.QueryTable(true, models.LockTable) table, err := lock.Malt.SQL.QueryTable(ctx, true, models.LockTable)
if err != nil { if err != nil {
return false return false
} }
@ -29,8 +31,8 @@ func (lock *Locker) TryLock() bool {
// TryUnlock attempts to unlock this WissKI and reports if it succeeded. // TryUnlock attempts to unlock this WissKI and reports if it succeeded.
// An unlock can only // An unlock can only
func (lock *Locker) TryUnlock() bool { func (lock *Locker) TryUnlock(ctx context.Context) bool {
table, err := lock.Malt.SQL.QueryTable(true, models.LockTable) table, err := lock.Malt.SQL.QueryTable(ctx, true, models.LockTable)
if err != nil { if err != nil {
return false return false
} }
@ -39,6 +41,6 @@ func (lock *Locker) TryUnlock() bool {
} }
// Unlock unlocks this WissKI, ignoring any error. // Unlock unlocks this WissKI, ignoring any error.
func (lock *Locker) Unlock() { func (lock *Locker) Unlock(ctx context.Context) {
lock.TryUnlock() lock.TryUnlock(ctx)
} }

View file

@ -1,14 +1,17 @@
package locker package locker
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/status"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
) )
// Locked checks if this WissKI is currently locked. // Locked checks if this WissKI is currently locked.
func (lock *Locker) Locked() (locked bool) { // If an error occurs, the instance is considered not locked.
table, err := lock.SQL.QueryTable(true, models.LockTable) func (lock *Locker) Locked(ctx context.Context) (locked bool) {
table, err := lock.SQL.QueryTable(ctx, true, models.LockTable)
if err != nil { if err != nil {
return false return false
} }
@ -19,6 +22,6 @@ func (lock *Locker) Locked() (locked bool) {
} }
func (locker *Locker) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (locker *Locker) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.Locked = locker.Locked() info.Locked = locker.Locked(flags.Context)
return return
} }

View file

@ -1,6 +1,8 @@
package mstore package mstore
import ( import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
) )
@ -14,26 +16,26 @@ type MStore struct {
// For is a Store for the provided value // For is a Store for the provided value
type For[Value any] meta.TypedKey[Value] type For[Value any] meta.TypedKey[Value]
func (f For[Value]) Get(m *MStore) (value Value, err error) { func (f For[Value]) Get(ctx context.Context, m *MStore) (value Value, err error) {
return meta.TypedKey[Value](f).Get(m.Storage) return meta.TypedKey[Value](f).Get(ctx, m.Storage)
} }
func (f For[Value]) GetAll(m *MStore) (values []Value, err error) { func (f For[Value]) GetAll(ctx context.Context, m *MStore) (values []Value, err error) {
return meta.TypedKey[Value](f).GetAll(m.Storage) return meta.TypedKey[Value](f).GetAll(ctx, m.Storage)
} }
func (f For[Value]) GetOrSet(m *MStore, dflt Value) (value Value, err error) { func (f For[Value]) GetOrSet(ctx context.Context, m *MStore, dflt Value) (value Value, err error) {
return meta.TypedKey[Value](f).GetOrSet(m.Storage, dflt) return meta.TypedKey[Value](f).GetOrSet(ctx, m.Storage, dflt)
} }
func (f For[Value]) Set(m *MStore, value Value) error { func (f For[Value]) Set(ctx context.Context, m *MStore, value Value) error {
return meta.TypedKey[Value](f).Set(m.Storage, value) return meta.TypedKey[Value](f).Set(ctx, m.Storage, value)
} }
func (f For[Value]) SetAll(m *MStore, values ...Value) error { func (f For[Value]) SetAll(ctx context.Context, m *MStore, values ...Value) error {
return meta.TypedKey[Value](f).SetAll(m.Storage, values...) return meta.TypedKey[Value](f).SetAll(ctx, m.Storage, values...)
} }
func (f For[Value]) Delete(m *MStore) error { func (f For[Value]) Delete(ctx context.Context, m *MStore) error {
return m.Storage.Delete(meta.Key(f)) return m.Storage.Delete(ctx, meta.Key(f))
} }

View file

@ -1,6 +1,7 @@
package extras package extras
import ( import (
"context"
_ "embed" _ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "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. // All returns the ids of all pathbuilders in consistent order.
// //
// server is the server to fetch the pathbuilders from, any may be nil. // server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) All(server *phpx.Server) (ids []string, err error) { func (pathbuilder *Pathbuilder) All(ctx context.Context, server *phpx.Server) (ids []string, err error) {
err = pathbuilder.PHP.ExecScript(server, &ids, pathbuilderPHP, "all_list") err = pathbuilder.PHP.ExecScript(ctx, server, &ids, pathbuilderPHP, "all_list")
slices.Sort(ids) slices.Sort(ids)
return 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. // 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. // 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) { func (pathbuilder *Pathbuilder) Get(ctx context.Context, server *phpx.Server, id string) (xml string, err error) {
err = pathbuilder.PHP.ExecScript(server, &xml, pathbuilderPHP, "one_xml", id) err = pathbuilder.PHP.ExecScript(ctx, server, &xml, pathbuilderPHP, "one_xml", id)
return return
} }
// GetAll returns all pathbuilders serialized as xml // GetAll returns all pathbuilders serialized as xml
// //
// server is the server to fetch the pathbuilders from, any may be nil. // 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) { func (pathbuilder *Pathbuilder) GetAll(ctx context.Context, server *phpx.Server) (pathbuilders map[string]string, err error) {
err = pathbuilder.PHP.ExecScript(server, &pathbuilders, pathbuilderPHP, "all_xml") err = pathbuilder.PHP.ExecScript(ctx, server, &pathbuilders, pathbuilderPHP, "all_xml")
return return
} }
@ -50,6 +51,6 @@ func (pathbuilder *Pathbuilder) Fetch(flags ingredient.FetcherFlags, info *statu
return return
} }
info.Pathbuilders, _ = pathbuilder.GetAll(flags.Server) info.Pathbuilders, _ = pathbuilder.GetAll(flags.Context, flags.Server)
return return
} }

View file

@ -2,6 +2,7 @@ package extras
import ( import (
"bufio" "bufio"
"context"
"path/filepath" "path/filepath"
"strings" "strings"
@ -37,8 +38,8 @@ var listURIPrefixesPHP string
// //
// server is an optional server to fetch prefixes from. // server is an optional server to fetch prefixes from.
// server may be nil. // server may be nil.
func (prefixes *Prefixes) All(server *phpx.Server) ([]string, error) { func (prefixes *Prefixes) All(ctx context.Context, server *phpx.Server) ([]string, error) {
uris, err := prefixes.database(server) uris, err := prefixes.database(ctx, server)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,9 +52,9 @@ func (prefixes *Prefixes) All(server *phpx.Server) ([]string, error) {
return append(uris, uris2...), nil 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 // 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 { if err != nil {
return nil, err return nil, err
} }
@ -143,28 +144,28 @@ func (wisski *Prefixes) filePrefixes() (prefixes []string, err error) {
var prefix = mstore.For[string]("prefix") var prefix = mstore.For[string]("prefix")
// Prefixes returns the cached prefixes from the given instance // Prefixes returns the cached prefixes from the given instance
func (wisski *Prefixes) AllCached() (results []string, err error) { func (wisski *Prefixes) AllCached(ctx context.Context) (results []string, err error) {
return prefix.GetAll(wisski.MStore) return prefix.GetAll(ctx, wisski.MStore)
} }
// Update updates the cached prefixes of this instance // Update updates the cached prefixes of this instance
func (wisski *Prefixes) Update() error { func (wisski *Prefixes) Update(ctx context.Context) error {
prefixes, err := wisski.All(nil) prefixes, err := wisski.All(ctx, nil)
if err != nil { if err != nil {
return err 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) { func (prefixes *Prefixes) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.NoPrefixes = prefixes.NoPrefix() info.NoPrefixes = prefixes.NoPrefix()
if flags.Quick { if flags.Quick {
// quick mode: grab only the cached prefixes // quick mode: grab only the cached prefixes
info.Prefixes, _ = prefixes.AllCached() info.Prefixes, _ = prefixes.AllCached(flags.Context)
} else { } else {
// slow mode: grab the fresh prefixes from the server // slow mode: grab the fresh prefixes from the server
// TODO: Do we want to update them while we are at it? // 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 return
} }

View file

@ -1,6 +1,7 @@
package extras package extras
import ( import (
"context"
_ "embed" _ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/phpx"
@ -17,11 +18,11 @@ type Settings struct {
//go:embed settings.php //go:embed settings.php
var settingsPHP string var settingsPHP string
func (settings *Settings) Get(server *phpx.Server, key string) (value any, err error) { func (settings *Settings) Get(ctx context.Context, server *phpx.Server, key string) (value any, err error) {
err = settings.PHP.ExecScript(server, &value, settingsPHP, "get_setting", key) err = settings.PHP.ExecScript(ctx, server, &value, settingsPHP, "get_setting", key)
return return
} }
func (settings *Settings) Set(server *phpx.Server, key string, value any) error { func (settings *Settings) Set(ctx context.Context, server *phpx.Server, key string, value any) error {
return settings.PHP.ExecScript(server, nil, settingsPHP, "set_setting", key, value) return settings.PHP.ExecScript(ctx, server, nil, settingsPHP, "set_setting", key, value)
} }

View file

@ -1,8 +1,8 @@
package extras package extras
import ( import (
"context"
_ "embed" _ "embed"
"log"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/phpx"
"github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/status"
@ -20,11 +20,8 @@ type Stats struct {
var statsPHP string var statsPHP string
// Get fetches all statistics from the server // Get fetches all statistics from the server
func (stats *Stats) Get(server *phpx.Server) (data status.Statistics, err error) { func (stats *Stats) Get(ctx context.Context, server *phpx.Server) (data status.Statistics, err error) {
err = stats.PHP.ExecScript(server, &data, statsPHP, "export_statistics") err = stats.PHP.ExecScript(ctx, server, &data, statsPHP, "export_statistics")
if err != nil {
log.Println(err)
}
return return
} }
@ -33,6 +30,6 @@ func (stats *Stats) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (e
return return
} }
info.Statistics, _ = stats.Get(flags.Server) info.Statistics, _ = stats.Get(flags.Context, flags.Server)
return return
} }

View file

@ -1,6 +1,7 @@
package php package php
import ( import (
"context"
"strings" "strings"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "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. // 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. // 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 { if server == nil {
server = php.NewServer() server = php.NewServer()
if err != nil { if err != nil {
@ -38,15 +39,15 @@ func (php *PHP) ExecScript(server *phpx.Server, value any, code string, entrypoi
} }
if code != "" { if code != "" {
if err := server.MarshalEval(nil, strings.TrimPrefix(code, "<?php")); err != nil { if err := server.MarshalEval(ctx, nil, strings.TrimPrefix(code, "<?php")); err != nil {
return err return err
} }
} }
return server.MarshalCall(value, entrypoint, args...) return server.MarshalCall(ctx, value, entrypoint, args...)
} }
func (php *PHP) EvalCode(server *phpx.Server, value any, code string) (err error) { func (php *PHP) EvalCode(ctx context.Context, server *phpx.Server, value any, code string) (err error) {
if server == nil { if server == nil {
server = php.NewServer() server = php.NewServer()
if err != nil { if err != nil {
@ -55,5 +56,5 @@ func (php *PHP) EvalCode(server *phpx.Server, value any, code string) (err error
defer server.Close() defer server.Close()
} }
return server.MarshalEval(value, code) return server.MarshalEval(ctx, value, code)
} }

View file

@ -1,6 +1,7 @@
package php package php
import ( import (
"context"
_ "embed" _ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/phpx" "github.com/FAU-CDI/wisski-distillery/internal/phpx"
@ -14,11 +15,12 @@ import (
// See [PHPServer]. // See [PHPServer].
func (php *PHP) NewServer() *phpx.Server { func (php *PHP) NewServer() *phpx.Server {
return &phpx.Server{ return &phpx.Server{
Context: context.Background(),
Executor: phpx.SpawnFunc(php.spawn), Executor: phpx.SpawnFunc(php.spawn),
} }
} }
func (php *PHP) spawn(str stream.IOStream, code string) error { func (php *PHP) spawn(ctx context.Context, str stream.IOStream, code string) error {
_, err := php.Barrel.Shell(str, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", code})) _, err := php.Barrel.Shell(ctx, str, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", code}))
return err return err
} }

View file

@ -15,11 +15,11 @@ import (
var errGetValidator = errors.New("GetPasswordValidator: Unknown Error") var errGetValidator = errors.New("GetPasswordValidator: Unknown Error")
func (u *Users) GetPasswordValidator(username string) (pv PasswordValidator, err error) { func (u *Users) GetPasswordValidator(ctx context.Context, username string) (pv PasswordValidator, err error) {
server := u.PHP.NewServer() server := u.PHP.NewServer()
var hash string var hash string
err = u.PHP.ExecScript(server, &hash, usersPHP, "get_password_hash", username) err = u.PHP.ExecScript(ctx, server, &hash, usersPHP, "get_password_hash", username)
if err != nil { if err != nil {
server.Close() server.Close()
return pv, err return pv, err
@ -46,9 +46,9 @@ func (pv PasswordValidator) Close() error {
return pv.server.Close() return pv.server.Close()
} }
func (pv PasswordValidator) Check(password string) bool { func (pv PasswordValidator) Check(ctx context.Context, password string) bool {
var result phpx.Boolean var result phpx.Boolean
err := pv.server.MarshalCall(&result, "check_password_hash", password, string(pv.hash)) err := pv.server.MarshalCall(ctx, &result, "check_password_hash", password, string(pv.hash))
if err != nil { if err != nil {
return false return false
} }
@ -65,10 +65,10 @@ func (cpe CommonPasswordError) Error() string {
return fmt.Sprintf("%q from %q", cpe.Password.Password, cpe.Password.Source) return fmt.Sprintf("%q from %q", cpe.Password.Password, cpe.Password.Source)
} }
func (pv PasswordValidator) CheckDictionary(context context.Context, writer io.Writer) error { func (pv PasswordValidator) CheckDictionary(ctx context.Context, writer io.Writer) error {
var counter int var counter int
if pv.Check(pv.username) { if pv.Check(ctx, pv.username) {
if writer != nil { if writer != nil {
counter++ counter++
fmt.Fprintln(writer, counter) fmt.Fprintln(writer, counter)
@ -76,10 +76,10 @@ func (pv PasswordValidator) CheckDictionary(context context.Context, writer io.W
return errPasswordUsername return errPasswordUsername
} }
for candidate := range CommonPasswords() { for candidate := range CommonPasswords() {
if context.Err() != nil { if ctx.Err() != nil {
continue continue
} }
result := pv.Check(candidate.Password) result := pv.Check(ctx, candidate.Password)
if writer != nil { if writer != nil {
counter++ counter++
fmt.Fprintln(writer, counter) fmt.Fprintln(writer, counter)
@ -90,7 +90,7 @@ func (pv PasswordValidator) CheckDictionary(context context.Context, writer io.W
} }
} }
return context.Err() return ctx.Err()
} }
//go:embed passwords //go:embed passwords

View file

@ -1,6 +1,7 @@
package users package users
import ( import (
"context"
_ "embed" _ "embed"
"errors" "errors"
"net/url" "net/url"
@ -21,19 +22,19 @@ type Users struct {
var usersPHP string var usersPHP string
// All returns all known usernames // All returns all known usernames
func (u *Users) All(server *phpx.Server) (users []status.User, err error) { func (u *Users) All(ctx context.Context, server *phpx.Server) (users []status.User, err error) {
err = u.PHP.ExecScript(server, &users, usersPHP, "list_users") err = u.PHP.ExecScript(ctx, server, &users, usersPHP, "list_users")
return return
} }
var errLoginUnknownError = errors.New("Login: Unknown Error") var errLoginUnknownError = errors.New("Login: Unknown Error")
// Login generates a login link for the user with the given username // Login generates a login link for the user with the given username
func (u *Users) Login(server *phpx.Server, username string) (dest *url.URL, err error) { func (u *Users) Login(ctx context.Context, server *phpx.Server, username string) (dest *url.URL, err error) {
// generate a (relative) link // generate a (relative) link
var path string var path string
err = u.PHP.ExecScript(server, &path, usersPHP, "get_login_link", username) err = u.PHP.ExecScript(ctx, server, &path, usersPHP, "get_login_link", username)
// if something went wrong, return // if something went wrong, return
if err != nil { if err != nil {
@ -57,9 +58,9 @@ func (u *Users) Login(server *phpx.Server, username string) (dest *url.URL, err
var errSetPassword = errors.New("SetPassword: Unknown Error") var errSetPassword = errors.New("SetPassword: Unknown Error")
// SetPassword sets the password for a given user // SetPassword sets the password for a given user
func (u *Users) SetPassword(server *phpx.Server, username, password string) error { func (u *Users) SetPassword(ctx context.Context, server *phpx.Server, username, password string) error {
var ok bool var ok bool
err := u.PHP.ExecScript(server, &ok, usersPHP, "set_user_password", username, password) err := u.PHP.ExecScript(ctx, server, &ok, usersPHP, "set_user_password", username, password)
if err != nil { if err != nil {
return err return err
} }
@ -74,6 +75,6 @@ func (u *Users) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err e
return return
} }
info.Users, _ = u.All(flags.Server) info.Users, _ = u.All(flags.Context, flags.Server)
return return
} }

View file

@ -1,10 +1,14 @@
package liquid package liquid
import "github.com/FAU-CDI/wisski-distillery/internal/models" import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/models"
)
// Snapshots returns the list of snapshots of this WissKI // Snapshots returns the list of snapshots of this WissKI
// NOTE(twiesing): Not entirely sure where this should go. // NOTE(twiesing): Not entirely sure where this should go.
// It's not that this is // It's not that this is
func (liquid *Liquid) Snapshots() (snapshots []models.Export, err error) { func (liquid *Liquid) Snapshots(ctx context.Context) (snapshots []models.Export, err error) {
return liquid.Malt.ExporterLog.For(liquid.Slug) return liquid.Malt.ExporterLog.For(ctx, liquid.Slug)
} }

56
pkg/cancel/context.go Normal file
View file

@ -0,0 +1,56 @@
package cancel
import (
"context"
)
// WithContext executes f and returns the returns the return value and nil.
//
// If the context is closed before f returns, invokes cancel and returns f(), ctx.Err().
//
// In general, WithContext always waits for f() to return even if cancel was called.
// As a special case if a closed context is passed, f is not invoked.
//
// allowcancel must be called by f exactly once, as soon as the cancel function may be invoked.
func WithContext[T any](ctx context.Context, f func(allowcancel func()) T, cancel func()) (t T, err error) {
t, _, err = WithContext2(ctx, func(start func()) (T, struct{}) {
return f(start), struct{}{}
}, cancel)
return
}
// WithContext2 is exactly like WithContext, but takes a function returning two parameters.
func WithContext2[T1, T2 any](ctx context.Context, f func(start func()) (T1, T2), cancel func()) (t1 T1, t2 T2, err error) {
// context is already closed, don't even try invoking it.
if err := ctx.Err(); err != nil {
return t1, t2, err
}
cancelable := make(chan struct{}, 1)
done := make(chan struct{})
go func() {
defer close(done)
defer close(cancelable)
t1, t2 = f(func() {
cancelable <- struct{}{}
})
}()
select {
case <-done:
// the function has exited regularly
// nothing to be done
case <-ctx.Done():
// context was cancelled
<-cancelable
cancel()
// still wait for it to be done!
<-done
err = ctx.Err()
}
return
}

57
pkg/cancel/copy.go Normal file
View file

@ -0,0 +1,57 @@
package cancel
import (
"context"
"io"
"time"
)
type SetDeadline interface {
SetDeadline(t time.Time)
}
type SetReadDeadline interface {
SetReadDeadline(t time.Time) error
}
type SetWriteDeadline interface {
SetWriteDeadline(t time.Time) error
}
// Copy reads from src, and copies to dst.
//
// If the context is closed before src is closed, attempts to close the underlying reader and writer.
func Copy(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
// if the context has a deadline, the propanate that deadline to the underyling file.
// this might cause the read call to not block.
if deadline, ok := ctx.Deadline(); ok {
var zero time.Time
if file, ok := src.(SetReadDeadline); ok {
file.SetReadDeadline(deadline)
defer file.SetReadDeadline(zero)
} else if file, ok := src.(SetDeadline); ok {
file.SetDeadline(deadline)
defer file.SetDeadline(zero)
}
if file, ok := dst.(SetWriteDeadline); ok {
file.SetWriteDeadline(deadline)
defer file.SetWriteDeadline(zero)
} else if file, ok := dst.(SetDeadline); ok {
file.SetDeadline(deadline)
defer file.SetDeadline(zero)
}
}
written, err, _ = WithContext2(ctx, func(start func()) (int64, error) {
start()
return io.Copy(dst, src)
}, func() {
if closer, ok := src.(io.Closer); ok {
closer.Close()
}
})
return written, err
}

View file

@ -45,7 +45,7 @@ type Environment interface {
DialContext(context context.Context, network, address string) (net.Conn, error) DialContext(context context.Context, network, address string) (net.Conn, error)
Executable() (string, error) Executable() (string, error)
Exec(io stream.IOStream, workdir string, exe string, argv ...string) int Exec(ctx context.Context, io stream.IOStream, workdir string, exe string, argv ...string) int
LookPathAbs(name string) (string, error) LookPathAbs(name string) (string, error)
} }

Some files were not shown because too many files have changed in this diff Show more