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
if !bk.NoPrune {
defer logging.LogOperation(func() error {
return dis.Exporter().PruneExports(context.IOStream)
return dis.Exporter().PruneExports(context.Context, context.IOStream)
}, context.IOStream, "Pruning old backups")
}
// do the handling
err := dis.Exporter().MakeExport(context.IOStream, exporter.ExportTask{
err := dis.Exporter().MakeExport(context.Context, context.IOStream, exporter.ExportTask{
Dest: bk.Positionals.Dest,
StagingOnly: bk.StagingOnly,

View file

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

View file

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

View file

@ -33,14 +33,14 @@ func (cron) Description() wisski_distillery.Description {
func (cr cron) Run(context wisski_distillery.Context) error {
// find all the instances!
wissKIs, err := context.Environment.Instances().Load(cr.Positionals.Slug...)
wissKIs, err := context.Environment.Instances().Load(context.Context, cr.Positionals.Slug...)
if err != nil {
return err
}
// and do the actual blind_update!
return status.StreamGroup(context.IOStream, cr.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error {
return instance.Drush().Cron(io)
return instance.Drush().Cron(context.Context, io)
}, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("cron %q", item.Slug)
}))

View file

@ -40,14 +40,14 @@ var errSettingSet = exit.Error{
}
func (ds setting) Run(context wisski_distillery.Context) error {
instance, err := context.Environment.Instances().WissKI(ds.Positionals.Slug)
instance, err := context.Environment.Instances().WissKI(context.Context, ds.Positionals.Slug)
if err != nil {
return err
}
if ds.Positionals.Value == "" {
// get the setting
value, err := instance.Settings().Get(nil, ds.Positionals.Setting)
value, err := instance.Settings().Get(context.Context, nil, ds.Positionals.Setting)
if err != nil {
return errSettingGet.Wrap(err)
}
@ -69,7 +69,7 @@ func (ds setting) Run(context wisski_distillery.Context) error {
}
// set the serialized value!
if err := instance.Settings().Set(nil, ds.Positionals.Setting, data); err != nil {
if err := instance.Settings().Set(context.Context, nil, ds.Positionals.Setting, data); err != nil {
return errSettingSet.Wrap(err)
}

View file

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

View file

@ -29,12 +29,12 @@ func (info) Description() wisski_distillery.Description {
}
func (i info) Run(context wisski_distillery.Context) error {
instance, err := context.Environment.Instances().WissKI(i.Positionals.Slug)
instance, err := context.Environment.Instances().WissKI(context.Context, i.Positionals.Slug)
if err != nil {
return err
}
info, err := instance.Info().Information(false)
info, err := instance.Info().Information(context.Context, false)
if err != nil {
return err
}

View file

@ -46,20 +46,20 @@ var errNotUnlock = exit.Error{
}
func (l instanceLock) Run(context wisski_distillery.Context) error {
instance, err := context.Environment.Instances().WissKI(l.Positionals.Slug)
instance, err := context.Environment.Instances().WissKI(context.Context, l.Positionals.Slug)
if err != nil {
return err
}
if l.Unlock {
if !instance.Locker().TryUnlock() {
if !instance.Locker().TryUnlock(context.Context) {
return errNotUnlock
}
context.Println("unlocked")
return nil
}
if !instance.Locker().TryLock() {
if !instance.Locker().TryLock(context.Context) {
return locker.Locked
}

View file

@ -25,7 +25,7 @@ func (ls) Description() wisski_distillery.Description {
}
func (l ls) Run(context wisski_distillery.Context) error {
instances, err := context.Environment.Instances().Load(l.Positionals.Slug...)
instances, err := context.Environment.Instances().Load(context.Context, l.Positionals.Slug...)
if err != nil {
return err
}

View file

@ -50,7 +50,7 @@ func (mma makeMysqlAccount) Run(context wisski_distillery.Context) error {
return errUnableToReadPassword.WithMessageF(err)
}
if err := dis.SQL().CreateSuperuser(username, password, false); err != nil {
if err := dis.SQL().CreateSuperuser(context.Context, username, password, false); err != nil {
return err
}

View file

@ -32,7 +32,7 @@ func (mysql) Description() wisski_distillery.Description {
}
func (ms mysql) Run(context wisski_distillery.Context) error {
code, err := context.Environment.SQL().Shell(context.IOStream, ms.Positionals.Args...)
code, err := context.Environment.SQL().Shell(context.Context, context.IOStream, ms.Positionals.Args...)
if err != nil {
return err
}

View file

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

View file

@ -31,12 +31,12 @@ var errPrefixesGeneric = exit.Error{
}
func (p prefixes) Run(context wisski_distillery.Context) error {
instance, err := context.Environment.Instances().WissKI(p.Positionals.Slug)
instance, err := context.Environment.Instances().WissKI(context.Context, p.Positionals.Slug)
if err != nil {
return err
}
prefixes, err := instance.Prefixes().All(nil)
prefixes, err := instance.Prefixes().All(context.Context, nil)
if err != nil {
return errPrefixesGeneric.Wrap(err)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -38,12 +38,12 @@ var errShell = exit.Error{
}
func (sh shell) Run(context wisski_distillery.Context) error {
instance, err := context.Environment.Instances().WissKI(sh.Positionals.Slug)
instance, err := context.Environment.Instances().WissKI(context.Context, sh.Positionals.Slug)
if err != nil {
return err
}
code, err := instance.Barrel().Shell(context.IOStream, sh.Positionals.Args...)
code, err := instance.Barrel().Shell(context.Context, context.IOStream, sh.Positionals.Args...)
if err != nil {
return errShell.WithMessageF(err)
}

View file

@ -39,13 +39,13 @@ func (sn snapshot) Run(context wisski_distillery.Context) error {
dis := context.Environment
// find the instance!
instance, err := dis.Instances().WissKI(sn.Positionals.Slug)
instance, err := dis.Instances().WissKI(context.Context, sn.Positionals.Slug)
if err != nil {
return err
}
// do a snapshot of it!
err = dis.Exporter().MakeExport(context.IOStream, exporter.ExportTask{
err = dis.Exporter().MakeExport(context.Context, context.IOStream, exporter.ExportTask{
Dest: sn.Positionals.Dest,
StagingOnly: sn.StagingOnly,

View file

@ -31,7 +31,7 @@ var errSSHListen = exit.Error{
func (s ssh) Run(context wisski_distillery.Context) error {
dis := context.Environment
server, err := dis.SSH().Server(dis.Context(), s.PrivateKeyPath, context.IOStream)
server, err := dis.SSH().Server(context.Context, s.PrivateKeyPath, context.IOStream)
if err != nil {
return err
}
@ -45,7 +45,7 @@ func (s ssh) Run(context wisski_distillery.Context) error {
}
go func() {
<-dis.Context().Done()
<-context.Context.Done()
listener.Close()
}()

View file

@ -25,7 +25,7 @@ func (cStatus) Description() wisski_distillery.Description {
}
func (s cStatus) Run(context wisski_distillery.Context) error {
status, _, err := context.Environment.Info().Status(true)
status, _, err := context.Environment.Info().Status(context.Context, true)
if err != nil {
return err
}

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

View file

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

View file

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

10
go.mod
View file

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

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/tkw1536/goprogram v0.1.1 h1:gamK9OuRqoX2yQlA/nkgfVHHZWd/u2uUj6vJMYrYa70=
github.com/tkw1536/goprogram v0.1.1/go.mod h1:Jqs0sTMzhrAGCX3JQrlEwQ0WRWQACCvuQQkaBDp65pE=
github.com/tkw1536/goprogram v0.2.0 h1:qoa5Izgq5gfVggkAcOtCwpjz4oZv1KgDEJ8BHIK/djQ=
github.com/tkw1536/goprogram v0.2.0/go.mod h1:Jqs0sTMzhrAGCX3JQrlEwQ0WRWQACCvuQQkaBDp65pE=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 h1:fGZugkZk2UgYBxtpKmvub51Yno1LJDeEsRp2xGD+0gY=
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -59,6 +65,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gorm.io/driver/mysql v1.3.6 h1:BhX1Y/RyALb+T9bZ3t07wLnPZBukt+IRkMn8UZSNbGM=
gorm.io/driver/mysql v1.3.6/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ=
gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.10 h1:4Ne9ZbzID9GUxRkllxN4WjJKpsHx8YbKvekVdgyWh24=
gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=

View file

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

View file

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

View file

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

View file

@ -16,13 +16,27 @@ func (home *Home) updateRedirect(ctx context.Context, io stream.IOStream) {
for t := range timex.TickContext(ctx, home.RefreshInterval) {
io.Printf("[%s]: reloading overrides\n", t.Format(time.Stamp))
redirect, _ := home.loadRedirect()
err := (func() error {
ctx, cancel := context.WithTimeout(ctx, home.RefreshInterval)
defer cancel()
redirect, err := home.loadRedirect(ctx)
if err != nil {
return err
}
home.redirect.Set(&redirect)
return nil
})()
if err != nil {
io.EPrintf("error reloading overrides: ", err.Error())
}
}
}()
}
func (home *Home) loadRedirect() (redirect Redirect, err error) {
func (home *Home) loadRedirect(ctx context.Context) (redirect Redirect, err error) {
if redirect.Overrides == nil {
redirect.Overrides = make(map[string]string)
}

View file

@ -52,7 +52,7 @@ func (info *Info) ingredients(r *http.Request) (cp ingredientsContext, err error
cp.Time = time.Now().UTC()
// find the instance itself!
instance, err := info.Instances.WissKI(mux.Vars(r)["slug"])
instance, err := info.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
if err == instances.ErrWissKINotFound {
return cp, httpx.ErrNotFound
}

View file

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

View file

@ -28,11 +28,11 @@ type Info struct {
func (*Info) Routes() []string { return []string{"/dis/"} }
func (info *Info) Handler(route string, context context.Context, io stream.IOStream) (handler http.Handler, err error) {
func (info *Info) Handler(ctx context.Context, route string, io stream.IOStream) (handler http.Handler, err error) {
router := mux.NewRouter()
{
socket := &httpx.WebSocket{
Context: context,
Context: ctx,
Fallback: router,
Handler: info.serveSocket,
}
@ -82,12 +82,12 @@ func (info *Info) Handler(route string, context context.Context, io stream.IOStr
}
// get the instance
instance, err := info.Instances.WissKI(r.PostFormValue("slug"))
instance, err := info.Instances.WissKI(r.Context(), r.PostFormValue("slug"))
if err != nil {
return "", httpx.ErrNotFound
}
target, err := instance.Users().Login(nil, r.PostFormValue("user"))
target, err := instance.Users().Login(r.Context(), nil, r.PostFormValue("user"))
if err != nil {
return "", err
}

View file

@ -29,7 +29,7 @@ type instanceContext struct {
func (info *Info) instance(r *http.Request) (is instanceContext, err error) {
// find the instance itself!
instance, err := info.Instances.WissKI(mux.Vars(r)["slug"])
instance, err := info.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
if err == instances.ErrWissKINotFound {
return is, httpx.ErrNotFound
}
@ -39,7 +39,7 @@ func (info *Info) instance(r *http.Request) (is instanceContext, err error) {
is.Instance = instance.Instance
// get some more info about the wisski
is.Info, err = instance.Info().Information(false)
is.Info, err = instance.Info().Information(r.Context(), false)
if err != nil {
return is, err
}

View file

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

View file

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

View file

@ -20,7 +20,7 @@ func (*Static) Routes() []string { return []string{"/static/"} }
//go:embed dist
var staticFS embed.FS
func (static *Static) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) {
func (static *Static) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) {
// take the filesystem
fs, err := fs.Sub(staticFS, "dist")
if err != nil {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package instances
import (
"context"
"errors"
"path/filepath"
@ -42,13 +43,13 @@ func (instances *Instances) use(wisski *wisski.WissKI) {
// WissKI returns the WissKI with the provided slug, if it exists.
// It the WissKI does not exist, returns ErrWissKINotFound.
func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err error) {
func (instances *Instances) WissKI(ctx context.Context, slug string) (wissKI *wisski.WissKI, err error) {
sql := instances.SQL
if err := sql.WaitQueryTable(); err != nil {
if err := sql.WaitQueryTable(ctx); err != nil {
return nil, err
}
table, err := sql.QueryTable(false, models.InstanceTable)
table, err := sql.QueryTable(ctx, false, models.InstanceTable)
if err != nil {
return nil, err
}
@ -72,8 +73,8 @@ func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err erro
// Instance is a convenience function to return an instance based on a model slug.
// When the instance does not exist, returns nil.
func (instances *Instances) Instance(instance models.Instance) *wisski.WissKI {
wissKI, err := instances.WissKI(instance.Slug)
func (instances *Instances) Instance(ctx context.Context, instance models.Instance) *wisski.WissKI {
wissKI, err := instances.WissKI(ctx, instance.Slug)
if err != nil {
return nil
}
@ -82,13 +83,13 @@ func (instances *Instances) Instance(instance models.Instance) *wisski.WissKI {
// Has checks if a WissKI with the provided slug exists inside the database.
// It does not perform any checks on the WissKI itself.
func (instances *Instances) Has(slug string) (ok bool, err error) {
func (instances *Instances) Has(ctx context.Context, slug string) (ok bool, err error) {
sql := instances.SQL
if err := sql.WaitQueryTable(); err != nil {
if err := sql.WaitQueryTable(ctx); err != nil {
return false, err
}
table, err := sql.QueryTable(false, models.InstanceTable)
table, err := sql.QueryTable(ctx, false, models.InstanceTable)
if err != nil {
return false, err
}
@ -103,37 +104,37 @@ func (instances *Instances) Has(slug string) (ok bool, err error) {
// All returns all instances of the WissKI Distillery in consistent order.
//
// There is no guarantee that this order remains identical between different api releases; however subsequent invocations are guaranteed to return the same order.
func (instances *Instances) All() ([]*wisski.WissKI, error) {
return instances.find(true, func(table *gorm.DB) *gorm.DB {
func (instances *Instances) All(ctx context.Context) ([]*wisski.WissKI, error) {
return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB {
return table
})
}
// WissKIs returns the WissKI instances with the provides slugs.
// If a slug does not exist, it is omitted from the result.
func (instances *Instances) WissKIs(slugs ...string) ([]*wisski.WissKI, error) {
return instances.find(true, func(table *gorm.DB) *gorm.DB {
func (instances *Instances) WissKIs(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) {
return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB {
return table.Where("slug IN ?", slugs)
})
}
// Load is like All, except that when no slugs are provided, it calls All.
func (instances *Instances) Load(slugs ...string) ([]*wisski.WissKI, error) {
func (instances *Instances) Load(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) {
if len(slugs) == 0 {
return instances.All()
return instances.All(ctx)
}
return instances.WissKIs(slugs...)
return instances.WissKIs(ctx, slugs...)
}
// find finds instances based on the provided query
func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) {
func (instances *Instances) find(ctx context.Context, order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) {
sql := instances.SQL
if err := sql.WaitQueryTable(); err != nil {
if err := sql.WaitQueryTable(ctx); err != nil {
return nil, err
}
// open the bookkeeping table
table, err := sql.QueryTable(false, models.InstanceTable)
table, err := sql.QueryTable(ctx, false, models.InstanceTable)
if err != nil {
return nil, err
}

View file

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

View file

@ -1,14 +1,18 @@
package meta
import "github.com/FAU-CDI/wisski-distillery/internal/models"
import (
"context"
"github.com/FAU-CDI/wisski-distillery/internal/models"
)
// Provision provisions new meta storage for this instance.
// NOTE(twiesing): This is a no-op, because we implement Purge.
func (meta *Meta) Provision(instance models.Instance, domain string) error {
func (meta *Meta) Provision(ctx context.Context, instance models.Instance, domain string) error {
return nil
}
// Purge purges the storage for the given instance.
func (meta *Meta) Purge(instance models.Instance, domain string) error {
return meta.Storage(instance.Slug).Purge()
func (meta *Meta) Purge(ctx context.Context, instance models.Instance, domain string) error {
return meta.Storage(instance.Slug).Purge(ctx)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -15,5 +15,5 @@ type Servable interface {
Routes() []string
// Handler returns the handler for the requested route
Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error)
Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error)
}

View file

@ -1,7 +1,6 @@
package solr
import (
"context"
"embed"
"path/filepath"
"time"
@ -15,7 +14,6 @@ type Solr struct {
BaseURL string // upstream solr url
PollContext context.Context // context to abort polling with
PollInterval time.Duration // duration to wait for during wait
}

View file

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

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

View file

@ -1,6 +1,7 @@
package sql
import (
"context"
"errors"
"github.com/FAU-CDI/wisski-distillery/internal/models"
@ -12,15 +13,15 @@ var errProvisionInvalidDatabaseParams = errors.New("Provision: Invalid parameter
var errProvisionInvalidGrant = errors.New("Provision: Grant failed")
// Provision provisions sql-specific resource for the given instance
func (sql *SQL) Provision(instance models.Instance, domain string) error {
return sql.CreateDatabase(instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword)
func (sql *SQL) Provision(ctx context.Context, instance models.Instance, domain string) error {
return sql.CreateDatabase(ctx, instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword)
}
// Purge purges sql-specific resources for the given instance
func (sql *SQL) Purge(instance models.Instance, domain string) error {
func (sql *SQL) Purge(ctx context.Context, instance models.Instance, domain string) error {
return errorx.First(
sql.PurgeDatabase(instance.SqlDatabase),
sql.PurgeUser(instance.SqlUsername),
sql.PurgeUser(ctx, instance.SqlUsername),
)
}
@ -28,7 +29,7 @@ func (sql *SQL) Purge(instance models.Instance, domain string) error {
// It then generates a new user, with the name 'user' and the password 'password', that is then granted access to this database.
//
// Provision internally waits for the database to become available.
func (sql *SQL) CreateDatabase(name, user, password string) error {
func (sql *SQL) CreateDatabase(ctx context.Context, name, user, password string) error {
// NOTE(twiesing): We shouldn't use string concat to build sql queries.
// But the driver doesn't support using query params for this particular query.
@ -43,14 +44,14 @@ func (sql *SQL) CreateDatabase(name, user, password string) error {
// Queries of the form "CREATE USER 'test'@'%' IDENTIFIED BY 'test'; FLUSH PRIVILEGES;" return error 1064 when using driver, but are fine with the shell.
// This should be fixed eventually, but I have no idea how.
if err := sql.unsafeWaitShell(); err != nil {
if err := sql.unsafeWaitShell(ctx); err != nil {
return err
}
query := "CREATE DATABASE `" + name + "`;" +
"CREATE USER '" + user + "'@'%' IDENTIFIED BY '" + password + "';" +
"GRANT ALL PRIVILEGES ON `" + name + "`.* TO `" + user + "`@`%`; FLUSH PRIVILEGES;"
if !sql.unsafeQueryShell(query) {
if !sql.unsafeQueryShell(ctx, query) {
return errProvisionInvalidGrant
}
@ -63,7 +64,7 @@ var errCreateSuperuserGrant = errors.New("CreateSuperUser: Grant failed")
// It then grants this user superuser status in the database.
//
// CreateSuperuser internally waits for the database to become available.
func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error {
func (sql *SQL) CreateSuperuser(ctx context.Context, user, password string, allowExisting bool) error {
// NOTE(twiesing): This function unsafely uses the shell directly to create a superuser.
// This is for two reasons:
// (1) this is used during bootstraping
@ -74,7 +75,7 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error
return errProvisionInvalidDatabaseParams
}
if err := sql.unsafeWaitShell(); err != nil {
if err := sql.unsafeWaitShell(ctx); err != nil {
return err
}
@ -85,7 +86,7 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error
query := "CREATE USER " + IfNotExists + " '" + user + "'@'%' IDENTIFIED BY '" + password + "';" +
"GRANT ALL PRIVILEGES ON *.* TO '" + user + "'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;"
if !sql.unsafeQueryShell(query) {
if !sql.unsafeQueryShell(ctx, query) {
return errCreateSuperuserGrant
}
@ -95,14 +96,14 @@ func (sql *SQL) CreateSuperuser(user, password string, allowExisting bool) error
var errPurgeUser = errors.New("PurgeUser: Failed to drop user")
// SQLPurgeUser deletes the specified user from the database
func (sql *SQL) PurgeUser(user string) error {
func (sql *SQL) PurgeUser(ctx context.Context, user string) error {
if !sqle.IsSafeDatabaseSingleQuote(user) {
return errPurgeUser
}
query := "DROP USER IF EXISTS '" + user + "'@'%';" +
"FLUSH PRIVILEGES;"
if !sql.unsafeQueryShell(query) {
if !sql.unsafeQueryShell(ctx, query) {
return errPurgeUser
}

View file

@ -1,6 +1,7 @@
package sql
import (
"context"
"io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
@ -12,19 +13,19 @@ func (*SQL) SnapshotNeedsRunning() bool { return false }
func (*SQL) SnapshotName() string { return "sql" }
func (sql *SQL) Snapshot(wisski models.Instance, context component.StagingContext) error {
return context.AddDirectory(".", func() error {
return context.AddFile(wisski.SqlDatabase+".sql", func(file io.Writer) error {
return sql.SnapshotDB(context.IO(), file, wisski.SqlDatabase)
func (sql *SQL) Snapshot(wisski models.Instance, scontext component.StagingContext) error {
return scontext.AddDirectory(".", func(ctx context.Context) error {
return scontext.AddFile(wisski.SqlDatabase+".sql", func(ctx context.Context, file io.Writer) error {
return sql.SnapshotDB(ctx, scontext.IO(), file, wisski.SqlDatabase)
})
})
}
// SnapshotDB makes a backup of the sql database into dest.
func (sql *SQL) SnapshotDB(io stream.IOStream, dest io.Writer, database string) error {
func (sql *SQL) SnapshotDB(ctx context.Context, io stream.IOStream, dest io.Writer, database string) error {
io = io.Streams(dest, nil, nil, 0).NonInteractive()
code, err := sql.Stack(sql.Environment).Exec(io, "sql", "mysqldump", "--databases", database)
code, err := sql.Stack(sql.Environment).Exec(ctx, io, "sql", "mysqldump", "--databases", database)
if err != nil {
return err
}

View file

@ -1,7 +1,6 @@
package sql
import (
"context"
"embed"
"path/filepath"
"time"
@ -16,7 +15,6 @@ type SQL struct {
ServerURL string // upstream server url
PollContext context.Context // context to abort polling with
PollInterval time.Duration // duration to wait for during wait
lazyNetwork lazy.Lazy[string]

View file

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

View file

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

View file

@ -4,6 +4,7 @@ package component
import (
"bufio"
"bytes"
"context"
"io/fs"
"path/filepath"
@ -33,9 +34,9 @@ var errStackUpdateBuild = errors.New("Stack.Update: Build returned non-zero exit
// This does not have a direct 'docker compose' shell equivalent.
//
// See also Up.
func (ds Stack) Update(io stream.IOStream, start bool) error {
func (ds Stack) Update(ctx context.Context, io stream.IOStream, start bool) error {
{
code, err := ds.compose(io, "pull")
code, err := ds.compose(ctx, io, "pull")
if err != nil {
return err
}
@ -45,7 +46,7 @@ func (ds Stack) Update(io stream.IOStream, start bool) error {
}
{
code, err := ds.compose(io, "build", "--pull")
code, err := ds.compose(ctx, io, "build", "--pull")
if err != nil {
return err
}
@ -54,7 +55,7 @@ func (ds Stack) Update(io stream.IOStream, start bool) error {
}
}
if start {
return ds.Up(io)
return ds.Up(ctx, io)
}
return nil
}
@ -63,8 +64,8 @@ var errStackUp = errors.New("Stack.Up: Up returned non-zero exit code")
// Up creates and starts the containers in this Stack.
// It is equivalent to 'docker compose up --remove-orphans --detach' on the shell.
func (ds Stack) Up(io stream.IOStream) error {
code, err := ds.compose(io, "up", "--remove-orphans", "--detach")
func (ds Stack) Up(ctx context.Context, io stream.IOStream) error {
code, err := ds.compose(ctx, io, "up", "--remove-orphans", "--detach")
if err != nil {
return err
}
@ -78,7 +79,7 @@ func (ds Stack) Up(io stream.IOStream) error {
// It is equivalent to 'docker compose exec $service $executable $args...'.
//
// It returns the exit code of the process.
func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...string) (int, error) {
func (ds Stack) Exec(ctx context.Context, io stream.IOStream, service, executable string, args ...string) (int, error) {
compose := []string{"exec"}
if io.StdinIsATerminal() {
compose = append(compose, "-ti")
@ -86,14 +87,14 @@ func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...str
compose = append(compose, service)
compose = append(compose, executable)
compose = append(compose, args...)
return ds.compose(io, compose...)
return ds.compose(ctx, io, compose...)
}
// Run runs a command in a running container with the given executable.
// It is equivalent to 'docker compose run [--rm] $service $executable $args...'.
//
// It returns the exit code of the process.
func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string, args ...string) (int, error) {
func (ds Stack) Run(ctx context.Context, io stream.IOStream, autoRemove bool, service, command string, args ...string) (int, error) {
compose := []string{"run"}
if autoRemove {
compose = append(compose, "--rm")
@ -104,7 +105,7 @@ func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string
compose = append(compose, service, command)
compose = append(compose, args...)
code, err := ds.compose(io, compose...)
code, err := ds.compose(ctx, io, compose...)
if err != nil {
return environment.ExecCommandError, nil
}
@ -115,8 +116,8 @@ var errStackRestart = errors.New("Stack.Restart: Restart returned non-zero exit
// Restart restarts all containers in this Stack.
// It is equivalent to 'docker compose restart' on the shell.
func (ds Stack) Restart(io stream.IOStream) error {
code, err := ds.compose(io, "restart")
func (ds Stack) Restart(ctx context.Context, io stream.IOStream) error {
code, err := ds.compose(ctx, io, "restart")
if err != nil {
return err
}
@ -129,12 +130,12 @@ func (ds Stack) Restart(io stream.IOStream) error {
var errStackPs = errors.New("Stack.Ps: Down returned non-zero exit code")
// Ps returns the ids of the containers currently running
func (ds Stack) Ps(io stream.IOStream) ([]string, error) {
func (ds Stack) Ps(ctx context.Context, io stream.IOStream) ([]string, error) {
// create a buffer
var buffer bytes.Buffer
// read the ids from the command!
code, err := ds.compose(io.Streams(&buffer, nil, nil, 0), "ps", "-q")
code, err := ds.compose(ctx, io.Streams(&buffer, nil, nil, 0), "ps", "-q")
if err != nil {
return nil, err
}
@ -162,8 +163,8 @@ var errStackDown = errors.New("Stack.Down: Down returned non-zero exit code")
// Down stops and removes all containers in this Stack.
// It is equivalent to 'docker compose down -v' on the shell.
func (ds Stack) Down(io stream.IOStream) error {
code, err := ds.compose(io, "down", "-v")
func (ds Stack) Down(ctx context.Context, io stream.IOStream) error {
code, err := ds.compose(ctx, io, "down", "-v")
if err != nil {
return err
}
@ -177,7 +178,7 @@ func (ds Stack) Down(io stream.IOStream) error {
//
// NOTE(twiesing): Check if this can be replaced by an internal call to libcompose.
// But probably not.
func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) {
func (ds Stack) compose(ctx context.Context, io stream.IOStream, args ...string) (int, error) {
if ds.DockerExecutable == "" {
var err error
ds.DockerExecutable, err = ds.Env.LookPathAbs("docker")
@ -185,7 +186,7 @@ func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) {
return environment.ExecCommandError, err
}
}
return ds.Env.Exec(io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil
return ds.Env.Exec(ctx, io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil
}
// StackWithResources represents a Stack that can be automatically installed from a set of resources.
@ -218,7 +219,7 @@ type InstallationContext map[string]string
//
// Installation is non-interactive, but will provide debugging output onto io.
// InstallationContext
func (is StackWithResources) Install(io stream.IOStream, context InstallationContext) error {
func (is StackWithResources) Install(ctx context.Context, io stream.IOStream, context InstallationContext) error {
env := is.Stack.Env
if is.ContextPath != "" {
// setup the base files
@ -277,7 +278,7 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon
// copy over file from context
io.Printf("[copy] %s (from %s)\n", dst, src)
if err := fsx.CopyFile(env, dst, src); err != nil {
if err := fsx.CopyFile(ctx, env, dst, src); err != nil {
return errors.Wrapf(err, "Unable to copy file %s", src)
}
}

View file

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

View file

@ -2,6 +2,7 @@ package triplestore
import (
"bytes"
"context"
"encoding/json"
"io"
"mime/multipart"
@ -30,7 +31,7 @@ type TriplestoreUserAppSettings struct {
//
// When bodyName is non-empty, expect body to be a byte slice representing a multipart/form-data upload with the given name.
// When bodyName is empty, simply marshal body as application/json
func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName string, accept string) (*http.Response, error) {
func (ts Triplestore) OpenRaw(ctx context.Context, method, url string, body interface{}, bodyName string, accept string) (*http.Response, error) {
var reader io.Reader
var contentType string
@ -66,7 +67,7 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str
DisableKeepAlives: true,
},
}
req, err := http.NewRequest(method, ts.BaseURL+url, reader)
req, err := http.NewRequestWithContext(ctx, method, ts.BaseURL+url, reader)
if err != nil {
return nil, err
}
@ -86,23 +87,23 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str
// Wait waits for the connection to the Triplestore to succeed.
// This is achieved using a polling strategy.
func (ts Triplestore) Wait() error {
func (ts Triplestore) Wait(ctx context.Context) error {
n := stream.FromNil()
return timex.TickUntilFunc(func(time.Time) bool {
res, err := ts.OpenRaw("GET", "/rest/repositories", nil, "", "")
res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "")
n.EPrintf("[Triplestore.Wait]: %s\n", err)
if err != nil {
return false
}
defer res.Body.Close()
return true
}, ts.PollContext, ts.PollInterval)
}, ctx, ts.PollInterval)
}
// PurgeUser deletes the specified user from the triplestore.
// When the user does not exist, returns no error.
func (ts Triplestore) PurgeUser(user string) error {
res, err := ts.OpenRaw("DELETE", "/rest/security/users/"+user, nil, "", "")
func (ts Triplestore) PurgeUser(ctx context.Context, user string) error {
res, err := ts.OpenRaw(ctx, "DELETE", "/rest/security/users/"+user, nil, "", "")
if err != nil {
return err
}
@ -114,8 +115,8 @@ func (ts Triplestore) PurgeUser(user string) error {
// PurgeRepo deletes the specified repo from the triplestore.
// When the repo does not exist, returns no error.
func (ts Triplestore) PurgeRepo(repo string) error {
res, err := ts.OpenRaw("DELETE", "/rest/repositories/"+repo, nil, "", "")
func (ts Triplestore) PurgeRepo(ctx context.Context, repo string) error {
res, err := ts.OpenRaw(ctx, "DELETE", "/rest/repositories/"+repo, nil, "", "")
if err != nil {
return err
}

View file

@ -2,6 +2,7 @@ package triplestore
import (
"bytes"
"context"
"net/http"
_ "embed"
@ -20,19 +21,19 @@ var errTripleStoreFailedRepository = exit.Error{
//go:embed create-repo.ttl
var createRepoTTL []byte
func (ts *Triplestore) Provision(instance models.Instance, domain string) error {
return ts.CreateRepository(instance.GraphDBRepository, domain, instance.GraphDBUsername, instance.GraphDBPassword)
func (ts *Triplestore) Provision(ctx context.Context, instance models.Instance, domain string) error {
return ts.CreateRepository(ctx, instance.GraphDBRepository, domain, instance.GraphDBUsername, instance.GraphDBPassword)
}
func (ts *Triplestore) Purge(instance models.Instance, domain string) error {
func (ts *Triplestore) Purge(ctx context.Context, instance models.Instance, domain string) error {
return errorx.First(
ts.PurgeRepo(instance.GraphDBRepository),
ts.PurgeUser(instance.GraphDBUsername),
ts.PurgeRepo(ctx, instance.GraphDBRepository),
ts.PurgeUser(ctx, instance.GraphDBUsername),
)
}
func (ts *Triplestore) CreateRepository(name, domain, user, password string) error {
if err := ts.Wait(); err != nil {
func (ts *Triplestore) CreateRepository(ctx context.Context, name, domain, user, password string) error {
if err := ts.Wait(ctx); err != nil {
return err
}
@ -48,7 +49,7 @@ func (ts *Triplestore) CreateRepository(name, domain, user, password string) err
// do the create!
{
res, err := ts.OpenRaw("POST", "/rest/repositories", createRepo.Bytes(), "config", "")
res, err := ts.OpenRaw(ctx, "POST", "/rest/repositories", createRepo.Bytes(), "config", "")
if err != nil {
return errTripleStoreFailedRepository.WithMessageF(err)
}
@ -60,7 +61,7 @@ func (ts *Triplestore) CreateRepository(name, domain, user, password string) err
// create the user and grant them access
{
res, err := ts.OpenRaw("POST", "/rest/security/users/"+user, TriplestoreUserPayload{
res, err := ts.OpenRaw(ctx, "POST", "/rest/security/users/"+user, TriplestoreUserPayload{
Password: password,
AppSettings: TriplestoreUserAppSettings{
DefaultInference: true,

View file

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

View file

@ -1,7 +1,6 @@
package triplestore
import (
"context"
"embed"
"path/filepath"
"time"
@ -15,7 +14,6 @@ type Triplestore struct {
BaseURL string // upstream server url
PollContext context.Context // context to abort polling with
PollInterval time.Duration // duration to wait for during wait
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package php
import (
"context"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/phpx"
@ -28,7 +29,7 @@ type PHP struct {
// It's arguments are encoded as json using [json.Marshal] and decoded within php.
//
// The return value of the function is again marshaled with json and returned to the caller.
func (php *PHP) ExecScript(server *phpx.Server, value any, code string, entrypoint string, args ...any) (err error) {
func (php *PHP) ExecScript(ctx context.Context, server *phpx.Server, value any, code string, entrypoint string, args ...any) (err error) {
if server == nil {
server = php.NewServer()
if err != nil {
@ -38,15 +39,15 @@ func (php *PHP) ExecScript(server *phpx.Server, value any, code string, entrypoi
}
if code != "" {
if err := server.MarshalEval(nil, strings.TrimPrefix(code, "<?php")); err != nil {
if err := server.MarshalEval(ctx, nil, strings.TrimPrefix(code, "<?php")); err != nil {
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 {
server = php.NewServer()
if err != nil {
@ -55,5 +56,5 @@ func (php *PHP) EvalCode(server *phpx.Server, value any, code string) (err error
defer server.Close()
}
return server.MarshalEval(value, code)
return server.MarshalEval(ctx, value, code)
}

View file

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

View file

@ -15,11 +15,11 @@ import (
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()
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 {
server.Close()
return pv, err
@ -46,9 +46,9 @@ func (pv PasswordValidator) Close() error {
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
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 {
return false
}
@ -65,10 +65,10 @@ func (cpe CommonPasswordError) Error() string {
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
if pv.Check(pv.username) {
if pv.Check(ctx, pv.username) {
if writer != nil {
counter++
fmt.Fprintln(writer, counter)
@ -76,10 +76,10 @@ func (pv PasswordValidator) CheckDictionary(context context.Context, writer io.W
return errPasswordUsername
}
for candidate := range CommonPasswords() {
if context.Err() != nil {
if ctx.Err() != nil {
continue
}
result := pv.Check(candidate.Password)
result := pv.Check(ctx, candidate.Password)
if writer != nil {
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

View file

@ -1,6 +1,7 @@
package users
import (
"context"
_ "embed"
"errors"
"net/url"
@ -21,19 +22,19 @@ type Users struct {
var usersPHP string
// All returns all known usernames
func (u *Users) All(server *phpx.Server) (users []status.User, err error) {
err = u.PHP.ExecScript(server, &users, usersPHP, "list_users")
func (u *Users) All(ctx context.Context, server *phpx.Server) (users []status.User, err error) {
err = u.PHP.ExecScript(ctx, server, &users, usersPHP, "list_users")
return
}
var errLoginUnknownError = errors.New("Login: Unknown Error")
// 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
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 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")
// 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
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 {
return err
}
@ -74,6 +75,6 @@ func (u *Users) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err e
return
}
info.Users, _ = u.All(flags.Server)
info.Users, _ = u.All(flags.Context, flags.Server)
return
}

View file

@ -1,10 +1,14 @@
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
// NOTE(twiesing): Not entirely sure where this should go.
// It's not that this is
func (liquid *Liquid) Snapshots() (snapshots []models.Export, err error) {
return liquid.Malt.ExporterLog.For(liquid.Slug)
func (liquid *Liquid) Snapshots(ctx context.Context) (snapshots []models.Export, err error) {
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)
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)
}

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