From 42b8cbd865dec823e0ad66eee544b872ae850233 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Tue, 18 Oct 2022 10:44:39 +0200 Subject: [PATCH] Move WissKI Parts to new ingredients system --- cmd/blind_update.go | 2 +- cmd/cron.go | 2 +- cmd/drupal_setting.go | 4 +- cmd/info.go | 2 +- cmd/instance_lock.go | 7 +- cmd/pathbuilders.go | 4 +- cmd/prefixes.go | 2 +- cmd/provision.go | 6 +- cmd/purge.go | 6 +- cmd/rebuild.go | 2 +- cmd/reserve.go | 2 +- cmd/shell.go | 2 +- cmd/update_prefix_config.go | 2 +- internal/dis/component.go | 4 - .../component/exporter/extras_pathbuilders.go | 2 +- internal/dis/component/exporter/snapshot.go | 8 +- internal/dis/component/home/public.go | 8 +- internal/dis/component/info/index.go | 16 +- internal/dis/component/info/instance.go | 6 +- internal/dis/component/info/socket.go | 6 +- internal/dis/component/instances/create.go | 24 +-- internal/dis/component/instances/instances.go | 20 +-- internal/dis/component/instances/malt/malt.go | 19 +++ internal/dis/component/meta/storage.go | 40 ----- internal/dis/component/resolver/prefixes.go | 4 +- internal/dis/distillery.go | 3 + .../barrel}/barrel.env | 0 internal/wisski/ingredient/barrel/barrel.go | 15 ++ .../barrel}/barrel/.dockerignore | 0 .../barrel}/barrel/Dockerfile | 0 .../barrel}/barrel/conf/ports.conf | 0 .../barrel}/barrel/conf/wisski.conf | 0 .../barrel}/barrel/conf/wisski.ini | 0 .../barrel}/barrel/docker-compose.yml | 0 .../barrel}/barrel/patch/easyrdf.patch | 0 .../barrel}/barrel/patch/triples.patch | 0 .../barrel}/barrel/scripts/entrypoint.sh | 0 .../barrel/scripts/provision_container.sh | 0 .../barrel}/barrel/scripts/user_shell.sh | 0 .../barrel/wisskiutils/create_adapter.php | 0 .../barrel/wisskiutils/set_trusted_host.sh | 0 .../barrel/wisskiutils/settings_php_get.sh | 0 .../barrel/wisskiutils/settings_php_set.sh | 0 internal/wisski/ingredient/barrel/build.go | 63 ++++++++ .../{ => ingredient/barrel/drush}/cron.go | 13 +- .../wisski/ingredient/barrel/drush/drush.go | 17 +++ .../wisski/ingredient/barrel/drush/update.go | 48 ++++++ .../barrel/provisioner/provisioner.go | 65 ++++++++ internal/wisski/ingredient/barrel/running.go | 12 ++ internal/wisski/ingredient/barrel/shell.go | 8 + internal/wisski/ingredient/barrel/stack.go | 43 ++++++ .../ingredient/bookkeeping/bookkeeping.go | 43 ++++++ internal/wisski/{ => ingredient/info}/info.go | 60 ++++---- internal/wisski/ingredient/ingredient.go | 48 ++++++ internal/wisski/ingredient/locker/lock.go | 44 ++++++ internal/wisski/ingredient/locker/locked.go | 15 ++ internal/wisski/ingredient/mstore/mstore.go | 43 ++++++ .../ingredient/php/extras/pathbuilder.go | 44 ++++++ .../php/extras/pathbuilder.php} | 0 .../php/extras/prefixes.go} | 63 ++++---- .../php/extras/prefixes.php} | 0 .../wisski/ingredient/php/extras/settings.go | 26 ++++ .../php/extras}/settings.php | 0 internal/wisski/ingredient/php/php.go | 58 ++++++++ .../php/server.go} | 104 ++++--------- .../wisski/{ => ingredient}/php/server.php | 0 .../reserve}/reserve.env | 0 internal/wisski/ingredient/reserve/reserve.go | 40 +++++ .../reserve}/reserve/docker-compose.yml | 0 .../reserve}/reserve/index.html | 0 internal/wisski/ingredients.go | 65 ++++++++ .../wisski/{props.go => liquid/domain.go} | 12 +- internal/wisski/liquid/liquid.go | 16 ++ internal/wisski/liquid/snapshots.go | 10 ++ internal/wisski/lock.go | 47 ------ internal/wisski/meta.go | 7 - internal/wisski/pathbuilders.go | 36 ----- internal/wisski/php.go | 17 --- internal/wisski/provision.go | 55 ------- internal/wisski/snapshots.go | 8 - internal/wisski/stack.go | 127 ---------------- internal/wisski/update.go | 47 ------ internal/wisski/wisski.go | 140 ++++++++++++------ 83 files changed, 1016 insertions(+), 646 deletions(-) create mode 100644 internal/dis/component/instances/malt/malt.go rename internal/wisski/{instances => ingredient/barrel}/barrel.env (100%) create mode 100644 internal/wisski/ingredient/barrel/barrel.go rename internal/wisski/{instances => ingredient/barrel}/barrel/.dockerignore (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/Dockerfile (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/conf/ports.conf (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/conf/wisski.conf (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/conf/wisski.ini (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/docker-compose.yml (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/patch/easyrdf.patch (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/patch/triples.patch (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/scripts/entrypoint.sh (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/scripts/provision_container.sh (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/scripts/user_shell.sh (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/wisskiutils/create_adapter.php (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/wisskiutils/set_trusted_host.sh (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/wisskiutils/settings_php_get.sh (100%) rename internal/wisski/{instances => ingredient/barrel}/barrel/wisskiutils/settings_php_set.sh (100%) create mode 100644 internal/wisski/ingredient/barrel/build.go rename internal/wisski/{ => ingredient/barrel/drush}/cron.go (58%) create mode 100644 internal/wisski/ingredient/barrel/drush/drush.go create mode 100644 internal/wisski/ingredient/barrel/drush/update.go create mode 100644 internal/wisski/ingredient/barrel/provisioner/provisioner.go create mode 100644 internal/wisski/ingredient/barrel/running.go create mode 100644 internal/wisski/ingredient/barrel/shell.go create mode 100644 internal/wisski/ingredient/barrel/stack.go create mode 100644 internal/wisski/ingredient/bookkeeping/bookkeeping.go rename internal/wisski/{ => ingredient/info}/info.go (54%) create mode 100644 internal/wisski/ingredient/ingredient.go create mode 100644 internal/wisski/ingredient/locker/lock.go create mode 100644 internal/wisski/ingredient/locker/locked.go create mode 100644 internal/wisski/ingredient/mstore/mstore.go create mode 100644 internal/wisski/ingredient/php/extras/pathbuilder.go rename internal/wisski/{php/export_pathbuilder.php => ingredient/php/extras/pathbuilder.php} (100%) rename internal/wisski/{prefix.go => ingredient/php/extras/prefixes.go} (57%) rename internal/wisski/{php/list_uri_prefixes.php => ingredient/php/extras/prefixes.php} (100%) create mode 100644 internal/wisski/ingredient/php/extras/settings.go rename internal/wisski/{php => ingredient/php/extras}/settings.php (100%) create mode 100644 internal/wisski/ingredient/php/php.go rename internal/wisski/{php_server.go => ingredient/php/server.go} (64%) rename internal/wisski/{ => ingredient}/php/server.php (100%) rename internal/wisski/{instances => ingredient/reserve}/reserve.env (100%) create mode 100644 internal/wisski/ingredient/reserve/reserve.go rename internal/wisski/{instances => ingredient/reserve}/reserve/docker-compose.yml (100%) rename internal/wisski/{instances => ingredient/reserve}/reserve/index.html (100%) create mode 100644 internal/wisski/ingredients.go rename internal/wisski/{props.go => liquid/domain.go} (61%) create mode 100644 internal/wisski/liquid/liquid.go create mode 100644 internal/wisski/liquid/snapshots.go delete mode 100644 internal/wisski/lock.go delete mode 100644 internal/wisski/meta.go delete mode 100644 internal/wisski/pathbuilders.go delete mode 100644 internal/wisski/php.go delete mode 100644 internal/wisski/provision.go delete mode 100644 internal/wisski/snapshots.go delete mode 100644 internal/wisski/stack.go delete mode 100644 internal/wisski/update.go diff --git a/cmd/blind_update.go b/cmd/blind_update.go index 31e12fc..8018505 100644 --- a/cmd/blind_update.go +++ b/cmd/blind_update.go @@ -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.BlindUpdate(str) + return instance.Drush().Update(str) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("blind_update %q", item.Slug) })) diff --git a/cmd/cron.go b/cmd/cron.go index 4ddb4a1..28e2cbd 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -40,7 +40,7 @@ func (cr cron) Run(context wisski_distillery.Context) error { // and do the actual blind_update! return status.StreamGroup(context.IOStream, cr.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { - return instance.Cron(io) + return instance.Drush().Cron(io) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("cron %q", item.Slug) })) diff --git a/cmd/drupal_setting.go b/cmd/drupal_setting.go index 88925d9..1037f80 100644 --- a/cmd/drupal_setting.go +++ b/cmd/drupal_setting.go @@ -47,7 +47,7 @@ func (ds setting) Run(context wisski_distillery.Context) error { if ds.Positionals.Value == "" { // get the setting - value, err := instance.GetSettingsPHP(nil, ds.Positionals.Setting) + value, err := instance.Settings().Get(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.SetSettingsPHP(nil, ds.Positionals.Setting, data); err != nil { + if err := instance.Settings().Set(nil, ds.Positionals.Setting, data); err != nil { return errSettingSet.Wrap(err) } diff --git a/cmd/info.go b/cmd/info.go index 5bad79c..6224a16 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -34,7 +34,7 @@ func (i info) Run(context wisski_distillery.Context) error { return err } - info, err := instance.Info(false) + info, err := instance.Info().Fetch(false) if err != nil { return err } diff --git a/cmd/instance_lock.go b/cmd/instance_lock.go index 6d35641..76ec934 100644 --- a/cmd/instance_lock.go +++ b/cmd/instance_lock.go @@ -3,6 +3,7 @@ package cmd import ( wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/internal/cli" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" "github.com/tkw1536/goprogram/exit" ) @@ -51,15 +52,15 @@ func (l instanceLock) Run(context wisski_distillery.Context) error { } if l.Unlock { - if !instance.Unlock() { + if !instance.Locker().TryUnlock() { return errNotUnlock } context.Println("unlocked") return nil } - if err := instance.TryLock(); err != nil { - return err + if !instance.Locker().TryLock() { + return locker.Locked } context.Println("locked") diff --git a/cmd/pathbuilders.go b/cmd/pathbuilders.go index da09f4e..8a80b00 100644 --- a/cmd/pathbuilders.go +++ b/cmd/pathbuilders.go @@ -46,7 +46,7 @@ func (pb pathbuilders) Run(context wisski_distillery.Context) error { // get all of the pathbuilders if pb.Positionals.Name == "" { - names, err := instance.Pathbuilders(nil) + names, err := instance.Pathbuilder().All(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(nil, pb.Positionals.Name) + xml, err := instance.Pathbuilder().Get(nil, pb.Positionals.Name) if xml == "" { return errNoPathbuilder.WithMessageF(pb.Positionals.Name) } diff --git a/cmd/prefixes.go b/cmd/prefixes.go index e6d369e..dd7698c 100644 --- a/cmd/prefixes.go +++ b/cmd/prefixes.go @@ -36,7 +36,7 @@ func (p prefixes) Run(context wisski_distillery.Context) error { return err } - prefixes, err := instance.Prefixes(nil) + prefixes, err := instance.Prefixes().All(nil) if err != nil { return errPrefixesGeneric.Wrap(err) } diff --git a/cmd/provision.go b/cmd/provision.go index 7b1d06a..bc9a6c3 100644 --- a/cmd/provision.go +++ b/cmd/provision.go @@ -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.Save(); err != nil { + if err := instance.Bookkeeping().Save(); err != nil { return errProvisionGeneric.WithMessageF(slug, 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.Provision(context.IOStream); err != nil { + if err := instance.Provisioner().Provision(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().Up(context.IOStream); err != nil { + if err := instance.Barrel().Stack().Up(context.IOStream); err != nil { return err } diff --git a/cmd/purge.go b/cmd/purge.go index fc0ca66..d825753 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -65,7 +65,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().Down(context.IOStream); err != nil { + if err := instance.Barrel().Stack().Down(context.IOStream); err != nil { context.EPrintln(err) } @@ -93,13 +93,13 @@ func (p purge) Run(context wisski_distillery.Context) error { // remove from bookkeeping logging.LogMessage(context.IOStream, "Removing instance from bookkeeping") - if err := instance.Delete(); err != nil { + if err := instance.Bookkeeping().Delete(); err != nil { context.EPrintln(err) } // remove the filesystem logging.LogMessage(context.IOStream, "Remove lock data", instance.FilesystemBase) - if !instance.Unlock() { + if instance.Locker().TryUnlock() { context.EPrintln("instance was not locked") } diff --git a/cmd/rebuild.go b/cmd/rebuild.go index 2f44509..8f40f06 100644 --- a/cmd/rebuild.go +++ b/cmd/rebuild.go @@ -47,7 +47,7 @@ func (rb rebuild) Run(context wisski_distillery.Context) error { // and do the actual rebuild return status.StreamGroup(context.IOStream, rb.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { - return instance.Build(io, true) + return instance.Barrel().Build(io, true) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("rebuild %q", item.Slug) })) diff --git a/cmd/reserve.go b/cmd/reserve.go index 706b943..eb0aa16 100644 --- a/cmd/reserve.go +++ b/cmd/reserve.go @@ -63,7 +63,7 @@ func (r reserve) Run(context wisski_distillery.Context) error { } // setup docker stack - s := instance.Reserve() + s := instance.Reserve().Stack() { if err := logging.LogOperation(func() error { return s.Install(context.IOStream, component.InstallationContext{}) diff --git a/cmd/shell.go b/cmd/shell.go index 3274163..3d2f667 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -43,7 +43,7 @@ func (sh shell) Run(context wisski_distillery.Context) error { return err } - code, err := instance.Shell(context.IOStream, sh.Positionals.Args...) + code, err := instance.Barrel().Shell(context.IOStream, sh.Positionals.Args...) if err != nil { return errShell.WithMessageF(err) } diff --git a/cmd/update_prefix_config.go b/cmd/update_prefix_config.go index 317cfca..970dfc0 100644 --- a/cmd/update_prefix_config.go +++ b/cmd/update_prefix_config.go @@ -44,7 +44,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error { return status.StreamGroup(context.IOStream, upc.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { io.Println("reading prefixes") - err := instance.UpdatePrefixes() + err := instance.Prefixes().UpdatePrefixes() if err != nil { return errPrefixUpdateFailed.Wrap(err) } diff --git a/internal/dis/component.go b/internal/dis/component.go index 782e2f6..a7ad522 100644 --- a/internal/dis/component.go +++ b/internal/dis/component.go @@ -34,10 +34,6 @@ func auto[C component.Component](context ctx) component.Component { // register returns all components of the distillery func (dis *Distillery) register(context ctx) []component.Component { - dis.poolInit.Do(func() { - dis.pool.Init = component.Init - }) - return collection.MapSlice( dis.allComponents(), func(f initFunc) component.Component { diff --git a/internal/dis/component/exporter/extras_pathbuilders.go b/internal/dis/component/exporter/extras_pathbuilders.go index c8c7d60..35c1297 100644 --- a/internal/dis/component/exporter/extras_pathbuilders.go +++ b/internal/dis/component/exporter/extras_pathbuilders.go @@ -19,7 +19,7 @@ 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).AllPathbuilders(nil) + builders, err := pbs.Instances.Instance(wisski).Pathbuilder().GetAll(nil) if err != nil { return err } diff --git a/internal/dis/component/exporter/snapshot.go b/internal/dis/component/exporter/snapshot.go index 597aa74..b5442e7 100644 --- a/internal/dis/component/exporter/snapshot.go +++ b/internal/dis/component/exporter/snapshot.go @@ -9,6 +9,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/status" @@ -46,7 +47,8 @@ type Snapshot struct { func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) { logging.LogMessage(io, "Locking instance") - if err := instance.TryLock(); err != nil { + if !instance.Locker().TryLock() { + err := locker.Locked io.EPrintln(err) logging.LogMessage(io, "Aborting snapshot creation") @@ -56,7 +58,7 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre } defer func() { logging.LogMessage(io, "Unlocking instance") - instance.Unlock() + instance.Locker().Unlock() }() // setup the snapshot @@ -85,7 +87,7 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Exporter, instance *wisski.WissKI, needsRunning bool) map[string]error { if !needsRunning && !snapshot.Description.Keepalive { - stack := instance.Barrel() + stack := instance.Barrel().Stack() logging.LogMessage(ios, "Stopping instance") snapshot.ErrStop = stack.Down(ios) diff --git a/internal/dis/component/home/public.go b/internal/dis/component/home/public.go index a51c2f1..cb57690 100644 --- a/internal/dis/component/home/public.go +++ b/internal/dis/component/home/public.go @@ -8,7 +8,7 @@ import ( _ "embed" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/static" - "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info" "github.com/FAU-CDI/wisski-distillery/pkg/timex" "github.com/tkw1536/goprogram/stream" "golang.org/x/sync/errgroup" @@ -65,7 +65,7 @@ func (home *Home) homeRender() ([]byte, error) { if err != nil { return nil, err } - context.Instances = make([]wisski.WissKIInfo, len(wissKIs)) + context.Instances = make([]info.WissKIInfo, len(wissKIs)) // determine their infos var eg errgroup.Group @@ -73,7 +73,7 @@ func (home *Home) homeRender() ([]byte, error) { i := i wissKI := instance eg.Go(func() (err error) { - context.Instances[i], err = wissKI.Info(true) + context.Instances[i], err = wissKI.Info().Fetch(true) return }) } @@ -86,7 +86,7 @@ func (home *Home) homeRender() ([]byte, error) { } type HomeContext struct { - Instances []wisski.WissKIInfo + Instances []info.WissKIInfo Time time.Time diff --git a/internal/dis/component/info/index.go b/internal/dis/component/info/index.go index 4d64e0f..f19d0ab 100644 --- a/internal/dis/component/info/index.go +++ b/internal/dis/component/info/index.go @@ -9,7 +9,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/static" "github.com/FAU-CDI/wisski-distillery/internal/models" - "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info" "golang.org/x/sync/errgroup" ) @@ -22,7 +22,7 @@ type indexPageContext struct { Config *config.Config - Instances []wisski.WissKIInfo + Instances []info.WissKIInfo TotalCount int RunningCount int @@ -31,18 +31,18 @@ type indexPageContext struct { Backups []models.Export } -func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error) { +func (nfo *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error) { var group errgroup.Group group.Go(func() error { // list all the instances - all, err := info.Instances.All() + all, err := nfo.Instances.All() if err != nil { return err } // get all of their info! - idx.Instances = make([]wisski.WissKIInfo, len(all)) + idx.Instances = make([]info.WissKIInfo, len(all)) for i, instance := range all { { i := i @@ -50,7 +50,7 @@ func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error // store the info for this group! group.Go(func() (err error) { - idx.Instances[i], err = instance.Info(true) + idx.Instances[i], err = instance.Info().Fetch(true) return err }) } @@ -61,12 +61,12 @@ func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error // get the log entries group.Go(func() (err error) { - idx.Backups, err = info.SnapshotsLog.For("") + idx.Backups, err = nfo.SnapshotsLog.For("") return }) // get the static properties - idx.Config = info.Config + idx.Config = nfo.Config idx.Time = time.Now().UTC() group.Wait() diff --git a/internal/dis/component/info/instance.go b/internal/dis/component/info/instance.go index 64d273c..777c813 100644 --- a/internal/dis/component/info/instance.go +++ b/internal/dis/component/info/instance.go @@ -9,7 +9,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/static" "github.com/FAU-CDI/wisski-distillery/internal/models" - "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" ) @@ -21,7 +21,7 @@ type instancePageContext struct { Time time.Time Instance models.Instance - Info wisski.WissKIInfo + Info info.WissKIInfo } func (info *Info) instancePageAPI(r *http.Request) (is instancePageContext, err error) { @@ -40,7 +40,7 @@ func (info *Info) instancePageAPI(r *http.Request) (is instancePageContext, err is.Instance = instance.Instance // get some more info about the wisski - is.Info, err = instance.Info(false) + is.Info, err = instance.Info().Fetch(false) if err != nil { return is, err } diff --git a/internal/dis/component/info/socket.go b/internal/dis/component/info/socket.go index 6999128..6c6c9b6 100644 --- a/internal/dis/component/info/socket.go +++ b/internal/dis/component/info/socket.go @@ -23,13 +23,13 @@ var socketInstanceActions = map[string]instanceActionFunc{ ) }, "rebuild": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error { - return instance.Build(str, true) + return instance.Barrel().Build(str, true) }, "update": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error { - return instance.BlindUpdate(str) + return instance.Drush().Update(str) }, "cron": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error { - return instance.Cron(str) + return instance.Drush().Cron(str) }, } diff --git a/internal/dis/component/instances/create.go b/internal/dis/component/instances/create.go index 70f2385..fcb9a20 100644 --- a/internal/dis/component/instances/create.go +++ b/internal/dis/component/instances/create.go @@ -25,37 +25,37 @@ func (instances *Instances) Create(slug string) (wissKI *wisski.WissKI, err erro wissKI = new(wisski.WissKI) instances.use(wissKI) - wissKI.Instance.Slug = slug - wissKI.Instance.FilesystemBase = filepath.Join(instances.Path(), wissKI.Domain()) + wissKI.Liquid.Instance.Slug = slug + wissKI.Liquid.Instance.FilesystemBase = filepath.Join(instances.Path(), wissKI.Domain()) - wissKI.Instance.OwnerEmail = "" - wissKI.Instance.AutoBlindUpdateEnabled = true + wissKI.Liquid.Instance.OwnerEmail = "" + wissKI.Liquid.Instance.AutoBlindUpdateEnabled = true // sql - wissKI.Instance.SqlDatabase = instances.Config.MysqlDatabasePrefix + slug - wissKI.Instance.SqlUsername = instances.Config.MysqlUserPrefix + slug + wissKI.Liquid.Instance.SqlDatabase = instances.Config.MysqlDatabasePrefix + slug + wissKI.Liquid.Instance.SqlUsername = instances.Config.MysqlUserPrefix + slug - wissKI.Instance.SqlPassword, err = instances.Config.NewPassword() + wissKI.Liquid.Instance.SqlPassword, err = instances.Config.NewPassword() if err != nil { return nil, err } // triplestore - wissKI.Instance.GraphDBRepository = instances.Config.GraphDBRepoPrefix + slug - wissKI.Instance.GraphDBUsername = instances.Config.GraphDBUserPrefix + slug + wissKI.Liquid.Instance.GraphDBRepository = instances.Config.GraphDBRepoPrefix + slug + wissKI.Liquid.Instance.GraphDBUsername = instances.Config.GraphDBUserPrefix + slug - wissKI.Instance.GraphDBPassword, err = instances.Config.NewPassword() + wissKI.Liquid.Instance.GraphDBPassword, err = instances.Config.NewPassword() if err != nil { return nil, err } // drupal - wissKI.DrupalUsername = "admin" // TODO: Change this! + wissKI.Liquid.DrupalUsername = "admin" // TODO: Change this! - wissKI.DrupalPassword, err = instances.Config.NewPassword() + wissKI.Liquid.DrupalPassword, err = instances.Config.NewPassword() if err != nil { return nil, err } diff --git a/internal/dis/component/instances/instances.go b/internal/dis/component/instances/instances.go index 5e868a5..e80f62f 100644 --- a/internal/dis/component/instances/instances.go +++ b/internal/dis/component/instances/instances.go @@ -6,10 +6,8 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/exit" @@ -21,10 +19,8 @@ import ( type Instances struct { component.Base - TS *triplestore.Triplestore - SQL *sql.SQL - Meta *meta.Meta - ExporterLog *logger.Logger + Malt *malt.Malt + SQL *sql.SQL } func (instances *Instances) Path() string { @@ -41,11 +37,7 @@ var errSQL = exit.Error{ // use uses the non-nil wisski instance with this instances func (instances *Instances) use(wisski *wisski.WissKI) { - wisski.Core = instances.Still - wisski.SQL = instances.SQL - wisski.TS = instances.TS - wisski.Meta = instances.Meta - wisski.ExporterLog = instances.ExporterLog + wisski.Liquid.Malt = instances.Malt } // WissKI returns the WissKI with the provided slug, if it exists. @@ -65,7 +57,7 @@ func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err erro wissKI = new(wisski.WissKI) // find the instance by slug - query := table.Where(&models.Instance{Slug: slug}).Find(&wissKI.Instance) + query := table.Where(&models.Instance{Slug: slug}).Find(&wissKI.Liquid.Instance) switch { case query.Error != nil: return nil, errSQL.WithMessageF(query.Error) @@ -166,7 +158,7 @@ func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB results = make([]*wisski.WissKI, len(bks)) for i, bk := range bks { results[i] = new(wisski.WissKI) - results[i].Instance = bk + results[i].Liquid.Instance = bk instances.use(results[i]) } diff --git a/internal/dis/component/instances/malt/malt.go b/internal/dis/component/instances/malt/malt.go new file mode 100644 index 0000000..d8931d2 --- /dev/null +++ b/internal/dis/component/instances/malt/malt.go @@ -0,0 +1,19 @@ +package malt + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore" +) + +// Malt is a component passed to every WissKI ingredient +type Malt struct { + component.Base + + TS *triplestore.Triplestore + SQL *sql.SQL + Meta *meta.Meta + ExporterLog *logger.Logger +} diff --git a/internal/dis/component/meta/storage.go b/internal/dis/component/meta/storage.go index 7bb9994..1267940 100644 --- a/internal/dis/component/meta/storage.go +++ b/internal/dis/component/meta/storage.go @@ -6,7 +6,6 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/models" - "github.com/tkw1536/goprogram/lib/collection" "gorm.io/gorm" ) @@ -177,42 +176,3 @@ func (s Storage) Purge() error { } return nil } - -// StorageFor returns a storage for the given key. -func StorageFor[Value any](key Key) func(storage *Storage) SpecifcStorage[Value] { - return func(storage *Storage) SpecifcStorage[Value] { - return SpecifcStorage[Value]{storage: storage, key: key} - } -} - -type SpecifcStorage[Value any] struct { - storage *Storage - key Key -} - -func (sf SpecifcStorage[Value]) Get() (value Value, err error) { - err = sf.storage.Get(sf.key, &value) - return -} - -func (sf SpecifcStorage[Value]) GetAll() (values []Value, err error) { - err = sf.storage.GetAll(sf.key, func(index, total int) any { - if values == nil { - values = make([]Value, total) - } - return &values[index] - }) - return values, err -} - -func (sf SpecifcStorage[Value]) Set(value Value) error { - return sf.storage.Set(sf.key, value) -} - -func (sf SpecifcStorage[Value]) SetAll(values ...Value) error { - return sf.storage.SetAll(sf.key, collection.AsAny(values)...) -} - -func (sf SpecifcStorage[Value]) Delete() error { - return sf.storage.Delete(sf.key) -} diff --git a/internal/dis/component/resolver/prefixes.go b/internal/dis/component/resolver/prefixes.go index 99b8422..b2ae2ff 100644 --- a/internal/dis/component/resolver/prefixes.go +++ b/internal/dis/component/resolver/prefixes.go @@ -30,14 +30,14 @@ func (resolver *Resolver) AllPrefixes() (map[string]string, error) { gPrefixes := make(map[string]string) var lastErr error for _, instance := range instances { - if instance.NoPrefix() { + if instance.Prefixes().NoPrefix() { continue } url := instance.URL().String() // failed to fetch prefixes for this particular instance // => skip it! - prefixes, err := instance.PrefixesCached() + prefixes, err := instance.Prefixes().PrefixesCached() if err != nil { lastErr = err continue diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index 574a608..51e0076 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -13,6 +13,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/home" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/info" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/resolver" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" @@ -114,8 +115,10 @@ func (dis *Distillery) allComponents() []initFunc { sql.PollInterval = time.Second }), + // instainces auto[*instances.Instances], auto[*meta.Meta], + auto[*malt.Malt], // Snapshots auto[*exporter.Exporter], diff --git a/internal/wisski/instances/barrel.env b/internal/wisski/ingredient/barrel/barrel.env similarity index 100% rename from internal/wisski/instances/barrel.env rename to internal/wisski/ingredient/barrel/barrel.env diff --git a/internal/wisski/ingredient/barrel/barrel.go b/internal/wisski/ingredient/barrel/barrel.go new file mode 100644 index 0000000..b2e2f86 --- /dev/null +++ b/internal/wisski/ingredient/barrel/barrel.go @@ -0,0 +1,15 @@ +package barrel + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore" +) + +// Barrel provides access to the underlying Barrel +type Barrel struct { + ingredient.Base + + Locker *locker.Locker + MStore *mstore.MStore +} diff --git a/internal/wisski/instances/barrel/.dockerignore b/internal/wisski/ingredient/barrel/barrel/.dockerignore similarity index 100% rename from internal/wisski/instances/barrel/.dockerignore rename to internal/wisski/ingredient/barrel/barrel/.dockerignore diff --git a/internal/wisski/instances/barrel/Dockerfile b/internal/wisski/ingredient/barrel/barrel/Dockerfile similarity index 100% rename from internal/wisski/instances/barrel/Dockerfile rename to internal/wisski/ingredient/barrel/barrel/Dockerfile diff --git a/internal/wisski/instances/barrel/conf/ports.conf b/internal/wisski/ingredient/barrel/barrel/conf/ports.conf similarity index 100% rename from internal/wisski/instances/barrel/conf/ports.conf rename to internal/wisski/ingredient/barrel/barrel/conf/ports.conf diff --git a/internal/wisski/instances/barrel/conf/wisski.conf b/internal/wisski/ingredient/barrel/barrel/conf/wisski.conf similarity index 100% rename from internal/wisski/instances/barrel/conf/wisski.conf rename to internal/wisski/ingredient/barrel/barrel/conf/wisski.conf diff --git a/internal/wisski/instances/barrel/conf/wisski.ini b/internal/wisski/ingredient/barrel/barrel/conf/wisski.ini similarity index 100% rename from internal/wisski/instances/barrel/conf/wisski.ini rename to internal/wisski/ingredient/barrel/barrel/conf/wisski.ini diff --git a/internal/wisski/instances/barrel/docker-compose.yml b/internal/wisski/ingredient/barrel/barrel/docker-compose.yml similarity index 100% rename from internal/wisski/instances/barrel/docker-compose.yml rename to internal/wisski/ingredient/barrel/barrel/docker-compose.yml diff --git a/internal/wisski/instances/barrel/patch/easyrdf.patch b/internal/wisski/ingredient/barrel/barrel/patch/easyrdf.patch similarity index 100% rename from internal/wisski/instances/barrel/patch/easyrdf.patch rename to internal/wisski/ingredient/barrel/barrel/patch/easyrdf.patch diff --git a/internal/wisski/instances/barrel/patch/triples.patch b/internal/wisski/ingredient/barrel/barrel/patch/triples.patch similarity index 100% rename from internal/wisski/instances/barrel/patch/triples.patch rename to internal/wisski/ingredient/barrel/barrel/patch/triples.patch diff --git a/internal/wisski/instances/barrel/scripts/entrypoint.sh b/internal/wisski/ingredient/barrel/barrel/scripts/entrypoint.sh similarity index 100% rename from internal/wisski/instances/barrel/scripts/entrypoint.sh rename to internal/wisski/ingredient/barrel/barrel/scripts/entrypoint.sh diff --git a/internal/wisski/instances/barrel/scripts/provision_container.sh b/internal/wisski/ingredient/barrel/barrel/scripts/provision_container.sh similarity index 100% rename from internal/wisski/instances/barrel/scripts/provision_container.sh rename to internal/wisski/ingredient/barrel/barrel/scripts/provision_container.sh diff --git a/internal/wisski/instances/barrel/scripts/user_shell.sh b/internal/wisski/ingredient/barrel/barrel/scripts/user_shell.sh similarity index 100% rename from internal/wisski/instances/barrel/scripts/user_shell.sh rename to internal/wisski/ingredient/barrel/barrel/scripts/user_shell.sh diff --git a/internal/wisski/instances/barrel/wisskiutils/create_adapter.php b/internal/wisski/ingredient/barrel/barrel/wisskiutils/create_adapter.php similarity index 100% rename from internal/wisski/instances/barrel/wisskiutils/create_adapter.php rename to internal/wisski/ingredient/barrel/barrel/wisskiutils/create_adapter.php diff --git a/internal/wisski/instances/barrel/wisskiutils/set_trusted_host.sh b/internal/wisski/ingredient/barrel/barrel/wisskiutils/set_trusted_host.sh similarity index 100% rename from internal/wisski/instances/barrel/wisskiutils/set_trusted_host.sh rename to internal/wisski/ingredient/barrel/barrel/wisskiutils/set_trusted_host.sh diff --git a/internal/wisski/instances/barrel/wisskiutils/settings_php_get.sh b/internal/wisski/ingredient/barrel/barrel/wisskiutils/settings_php_get.sh similarity index 100% rename from internal/wisski/instances/barrel/wisskiutils/settings_php_get.sh rename to internal/wisski/ingredient/barrel/barrel/wisskiutils/settings_php_get.sh diff --git a/internal/wisski/instances/barrel/wisskiutils/settings_php_set.sh b/internal/wisski/ingredient/barrel/barrel/wisskiutils/settings_php_set.sh similarity index 100% rename from internal/wisski/instances/barrel/wisskiutils/settings_php_set.sh rename to internal/wisski/ingredient/barrel/barrel/wisskiutils/settings_php_set.sh diff --git a/internal/wisski/ingredient/barrel/build.go b/internal/wisski/ingredient/barrel/build.go new file mode 100644 index 0000000..57b7e23 --- /dev/null +++ b/internal/wisski/ingredient/barrel/build.go @@ -0,0 +1,63 @@ +package barrel + +import ( + "time" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore" + "github.com/tkw1536/goprogram/stream" +) + +// 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() { + err := locker.Locked + return err + } + defer barrel.Locker.Unlock() + + stack := barrel.Stack() + + var context component.InstallationContext + + { + err := stack.Install(stream, context) + if err != nil { + return err + } + } + + { + err := stack.Update(stream, start) + if err != nil { + return err + } + } + + // store the current last rebuild + return barrel.setLastRebuild() +} + +// 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) + if err == meta.ErrMetadatumNotSet { + return t, nil + } + if err != nil { + return t, err + } + + // and turn it into time! + return time.Unix(epoch, 0), nil +} + +func (barrel *Barrel) setLastRebuild() error { + return lastRebuild.Set(barrel.MStore, time.Now().Unix()) +} diff --git a/internal/wisski/cron.go b/internal/wisski/ingredient/barrel/drush/cron.go similarity index 58% rename from internal/wisski/cron.go rename to internal/wisski/ingredient/barrel/drush/cron.go index 32fadf3..a88b84d 100644 --- a/internal/wisski/cron.go +++ b/internal/wisski/ingredient/barrel/drush/cron.go @@ -1,8 +1,9 @@ -package wisski +package drush import ( "time" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" "github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/stream" ) @@ -12,23 +13,23 @@ var errCronFailed = exit.Error{ ExitCode: exit.ExitGeneric, } -func (wisski *WissKI) Cron(io stream.IOStream) error { - code, err := wisski.Shell(io, "/runtime/cron.sh") +func (drush *Drush) Cron(io stream.IOStream) error { + code, err := drush.Barrel.Shell(io, "/runtime/cron.sh") if err != nil { io.EPrintln(err) } if code != 0 { // keep going, because we want to run as many crons as possible - err = errBlindUpdateFailed.WithMessageF(wisski.Slug, code) + err = errCronFailed.WithMessageF(drush.Slug, code) io.EPrintln(err) } return nil } -func (wisski *WissKI) LastCron(server *PHPServer) (t time.Time, err error) { +func (drush *Drush) LastCron(server *php.Server) (t time.Time, err error) { var timestamp int64 - err = wisski.EvalPHPCode(server, ×tamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `) + err = drush.PHP.EvalCode(server, ×tamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `) if err != nil { return } diff --git a/internal/wisski/ingredient/barrel/drush/drush.go b/internal/wisski/ingredient/barrel/drush/drush.go new file mode 100644 index 0000000..4b81724 --- /dev/null +++ b/internal/wisski/ingredient/barrel/drush/drush.go @@ -0,0 +1,17 @@ +package drush + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" +) + +// Drush implements commands related to drush +type Drush struct { + ingredient.Base + + Barrel *barrel.Barrel + MStore *mstore.MStore + PHP *php.PHP +} diff --git a/internal/wisski/ingredient/barrel/drush/update.go b/internal/wisski/ingredient/barrel/drush/update.go new file mode 100644 index 0000000..3b2c186 --- /dev/null +++ b/internal/wisski/ingredient/barrel/drush/update.go @@ -0,0 +1,48 @@ +package drush + +import ( + "time" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore" + "github.com/FAU-CDI/wisski-distillery/pkg/environment" + "github.com/tkw1536/goprogram/exit" + "github.com/tkw1536/goprogram/stream" +) + +var errBlindUpdateFailed = exit.Error{ + Message: "Failed to run blind update script for instance %q: exited with code %s", + ExitCode: exit.ExitGeneric, +} + +// Update performs a blind drush update +func (drush *Drush) Update(io stream.IOStream) error { + code, err := drush.Barrel.Shell(io, "/runtime/blind_update.sh") + if err != nil { + return errBlindUpdateFailed.WithMessageF(drush.Slug, environment.ExecCommandError) + } + if code != 0 { + return errBlindUpdateFailed.WithMessageF(drush.Slug, code) + } + + return drush.setLastUpdate() +} + +const lastUpdate = mstore.For[int64]("lastUpdate") + +func (drush *Drush) LastUpdate() (t time.Time, err error) { + epoch, err := lastUpdate.Get(drush.MStore) + if err == meta.ErrMetadatumNotSet { + return t, nil + } + if err != nil { + return t, err + } + + // and turn it into time! + return time.Unix(epoch, 0), nil +} + +func (drush *Drush) setLastUpdate() error { + return lastUpdate.Set(drush.MStore, time.Now().Unix()) +} diff --git a/internal/wisski/ingredient/barrel/provisioner/provisioner.go b/internal/wisski/ingredient/barrel/provisioner/provisioner.go new file mode 100644 index 0000000..ed03c55 --- /dev/null +++ b/internal/wisski/ingredient/barrel/provisioner/provisioner.go @@ -0,0 +1,65 @@ +package provisioner + +import ( + "errors" + "strings" + + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel" + "github.com/alessio/shellescape" + "github.com/tkw1536/goprogram/stream" +) + +// Provisioner provides provisioning for a barrel +// NOTE(twiesing): This should be refactored to not use the provision script. +// Instead, this should code directly defined in go. +type Provisioner struct { + ingredient.Base + Barrel *barrel.Barrel +} + +// Provision provisions an instance, assuming that the required databases already exist. +func (provision *Provisioner) Provision(io stream.IOStream) error { + + // build the container + if err := provision.Barrel.Build(io, false); err != nil { + return err + } + + provisionParams := []string{ + provision.Domain(), + + provision.SqlDatabase, + provision.SqlUsername, + provision.SqlPassword, + + provision.GraphDBRepository, + provision.GraphDBUsername, + provision.GraphDBPassword, + + provision.DrupalUsername, + provision.DrupalPassword, + + "", // TODO: DrupalVersion + "", // TODO: WissKIVersion + } + + // escape the parameter + for i, param := range provisionParams { + provisionParams[i] = shellescape.Quote(param) + } + + // figure out the provision script + // 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) + if err != nil { + return err + } + if code != 0 { + return errors.New("unable to run provision script") + } + + return nil +} diff --git a/internal/wisski/ingredient/barrel/running.go b/internal/wisski/ingredient/barrel/running.go new file mode 100644 index 0000000..f6d41a4 --- /dev/null +++ b/internal/wisski/ingredient/barrel/running.go @@ -0,0 +1,12 @@ +package barrel + +import "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()) + if err != nil { + return false, err + } + return len(ps) > 0, nil +} diff --git a/internal/wisski/ingredient/barrel/shell.go b/internal/wisski/ingredient/barrel/shell.go new file mode 100644 index 0000000..5cb8365 --- /dev/null +++ b/internal/wisski/ingredient/barrel/shell.go @@ -0,0 +1,8 @@ +package barrel + +import "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...)...) +} diff --git a/internal/wisski/ingredient/barrel/stack.go b/internal/wisski/ingredient/barrel/stack.go new file mode 100644 index 0000000..596e23b --- /dev/null +++ b/internal/wisski/ingredient/barrel/stack.go @@ -0,0 +1,43 @@ +package barrel + +import ( + "embed" + "path/filepath" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" +) + +//go:embed all:barrel barrel.env +var barrelResources embed.FS + +// Barrel returns a stack representing the running WissKI Instance +func (barrel *Barrel) Stack() component.StackWithResources { + return component.StackWithResources{ + Stack: component.Stack{ + Dir: barrel.FilesystemBase, + Env: barrel.Malt.Environment, + }, + + Resources: barrelResources, + ContextPath: filepath.Join("barrel"), + EnvPath: filepath.Join("barrel.env"), + + EnvContext: map[string]string{ + "DOCKER_NETWORK_NAME": barrel.Malt.Config.DockerNetworkName, + + "SLUG": barrel.Slug, + "VIRTUAL_HOST": barrel.Domain(), + "HTTPS_ENABLED": barrel.Malt.Config.HTTPSEnabledEnv(), + + "DATA_PATH": filepath.Join(barrel.FilesystemBase, "data"), + "RUNTIME_DIR": barrel.Malt.Config.RuntimeDir(), + "GLOBAL_AUTHORIZED_KEYS_FILE": barrel.Malt.Config.GlobalAuthorizedKeysFile, + }, + + MakeDirs: []string{"data", ".composer"}, + + TouchFiles: []string{ + filepath.Join("data", "authorized_keys"), + }, + } +} diff --git a/internal/wisski/ingredient/bookkeeping/bookkeeping.go b/internal/wisski/ingredient/bookkeeping/bookkeeping.go new file mode 100644 index 0000000..51ed685 --- /dev/null +++ b/internal/wisski/ingredient/bookkeeping/bookkeeping.go @@ -0,0 +1,43 @@ +package bookkeeping + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" +) + +// Bookkeeping provides instance bookkeeping +type Bookkeeping struct { + ingredient.Base +} + +// Save saves this instance in the bookkeeping table +func (bk *Bookkeeping) Save() error { + sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable) + if err != nil { + return err + } + + // it has never been created => we need to create it in the database + if bk.Instance.Created.IsZero() { + return sdb.Create(&bk.Instance).Error + } + + // Update based on the primary key! + return sdb.Where("pk = ?", bk.Instance.Pk).Updates(&bk.Instance).Error +} + +// Delete deletes this instance from the bookkeeping table +func (bk *Bookkeeping) Delete() error { + sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable) + if err != nil { + return err + } + + // doesn't exist => nothing to delete + if bk.Instance.Created.IsZero() { + return nil + } + + // delete it directly + return sdb.Delete(&bk.Instance).Error +} diff --git a/internal/wisski/info.go b/internal/wisski/ingredient/info/info.go similarity index 54% rename from internal/wisski/info.go rename to internal/wisski/ingredient/info/info.go index 132c42e..bd0e02c 100644 --- a/internal/wisski/info.go +++ b/internal/wisski/ingredient/info/info.go @@ -1,13 +1,29 @@ -package wisski +package info import ( "time" "github.com/FAU-CDI/wisski-distillery/internal/models" - "github.com/tkw1536/goprogram/stream" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/drush" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/extras" "golang.org/x/sync/errgroup" ) +type Info struct { + ingredient.Base + + PHP *php.PHP + Barrel *barrel.Barrel + Locker *locker.Locker + Drush *drush.Drush + Prefixes *extras.Prefixes + Pathbuilder *extras.Pathbuilder +} + // WissKIInfo represents information about this WissKI Instance. type WissKIInfo struct { Time time.Time // Time this info was built @@ -33,14 +49,14 @@ type WissKIInfo struct { Pathbuilders map[string]string // all the pathbuilders } -// Info fetches information about this WissKI. +// Fetch fetches information about this WissKI. // TODO: Rework this to be able to determine what kind of information is available. -func (wisski *WissKI) Info(quick bool) (info WissKIInfo, err error) { +func (wisski *Info) Fetch(quick bool) (info WissKIInfo, err error) { var group errgroup.Group wisski.infoQuick(&info, &group) if !quick { - server, err := wisski.NewPHPServer() + server, err := wisski.PHP.NewServer() if err == nil { defer server.Close() } @@ -51,45 +67,40 @@ func (wisski *WissKI) Info(quick bool) (info WissKIInfo, err error) { return } -func (wisski *WissKI) infoQuick(info *WissKIInfo, group *errgroup.Group) { +func (wisski *Info) infoQuick(info *WissKIInfo, group *errgroup.Group) { info.Time = time.Now().UTC() info.Slug = wisski.Slug info.URL = wisski.URL().String() group.Go(func() (err error) { - info.Running, err = wisski.Running() + info.Running, err = wisski.Barrel.Running() return }) group.Go(func() (err error) { - info.Locked = wisski.IsLocked() + info.Locked = wisski.Locker.Locked() return }) group.Go(func() (err error) { - info.LastRebuild, _ = wisski.LastRebuild() + info.LastRebuild, _ = wisski.Barrel.LastRebuild() return }) group.Go(func() (err error) { - info.LastUpdate, _ = wisski.LastUpdate() + info.LastUpdate, _ = wisski.Drush.LastUpdate() return }) group.Go(func() (err error) { - info.LastRebuild, _ = wisski.LastRebuild() - return - }) - - group.Go(func() (err error) { - info.NoPrefixes = wisski.NoPrefix() + info.NoPrefixes = wisski.Prefixes.NoPrefix() return }) } -func (wisski *WissKI) infoSlow(info *WissKIInfo, server *PHPServer, group *errgroup.Group) { +func (wisski *Info) infoSlow(info *WissKIInfo, server *php.Server, group *errgroup.Group) { group.Go(func() (err error) { - info.Prefixes, _ = wisski.Prefixes(server) + info.Prefixes, _ = wisski.Prefixes.All(server) return nil }) @@ -99,21 +110,12 @@ func (wisski *WissKI) infoSlow(info *WissKIInfo, server *PHPServer, group *errgr }) group.Go(func() (err error) { - info.Pathbuilders, _ = wisski.AllPathbuilders(server) + info.Pathbuilders, _ = wisski.Pathbuilder.GetAll(server) return nil }) group.Go(func() (err error) { - info.LastCron, _ = wisski.LastCron(server) + info.LastCron, _ = wisski.Drush.LastCron(server) return }) } - -// Running checks if this WissKI is currently running. -func (wisski *WissKI) Running() (bool, error) { - ps, err := wisski.Barrel().Ps(stream.FromNil()) - if err != nil { - return false, err - } - return len(ps) > 0, nil -} diff --git a/internal/wisski/ingredient/ingredient.go b/internal/wisski/ingredient/ingredient.go new file mode 100644 index 0000000..e1ca97c --- /dev/null +++ b/internal/wisski/ingredient/ingredient.go @@ -0,0 +1,48 @@ +package ingredient + +import ( + "reflect" + "strings" + + "github.com/FAU-CDI/wisski-distillery/internal/wisski/liquid" +) + +// Ingredients represent a part of a WissKI instance. +// An Ingredient should be implemented as a pointer to a struct. +// Every ingredient must embed [Base] and should be initialized using [Init] inside a [lazy.Pool]. +// +// By convention these are defined within their corresponding subpackage. +// This subpackage also contains all required resources. +type Ingredient interface { + // Name returns the name of this ingredient + // Name should be implemented by the [Base] struct. + Name() string + + // getBase returns the underlying Base object of this Ingredient. + // It is used internally during initialization + getBase() *Base +} + +// Base is embedded into every Ingredient +type Base struct { + name string // name is the name of this ingredient + *liquid.Liquid // the underlying liquid +} + +//lint:ignore U1000 used to implement the private methods of [Component] +func (cb *Base) getBase() *Base { + return cb +} + +// Init initializes a new Ingredient. +// Init is only intended to be used within a lazy.Pool[Ingredient,*Liquid]. +func Init(ingredient Ingredient, liquid *liquid.Liquid) Ingredient { + base := ingredient.getBase() // pointer to a struct + base.Liquid = liquid + base.name = strings.ToLower(reflect.TypeOf(ingredient).Elem().Name()) + return ingredient +} + +func (cb Base) Name() string { + return cb.name +} diff --git a/internal/wisski/ingredient/locker/lock.go b/internal/wisski/ingredient/locker/lock.go new file mode 100644 index 0000000..94014d1 --- /dev/null +++ b/internal/wisski/ingredient/locker/lock.go @@ -0,0 +1,44 @@ +package locker + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/tkw1536/goprogram/exit" +) + +// Locker provides facitilites for locking this WissKI instance +type Locker struct { + ingredient.Base +} + +var Locked = exit.Error{ + Message: "WissKI Instance is locked for administrative operations", + ExitCode: exit.ExitGeneric, +} + +// 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) + if err != nil { + return false + } + + result := table.FirstOrCreate(&models.Lock{}, models.Lock{Slug: lock.Slug}) + return result.Error == nil && result.RowsAffected == 1 +} + +// 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) + if err != nil { + return false + } + result := table.Where("slug = ?", lock.Slug).Delete(&models.Lock{}) + return result.Error == nil && result.RowsAffected == 1 +} + +// Unlock unlocks this WissKI, ignoring any error. +func (lock *Locker) Unlock() { + lock.TryUnlock() +} diff --git a/internal/wisski/ingredient/locker/locked.go b/internal/wisski/ingredient/locker/locked.go new file mode 100644 index 0000000..d91e2f0 --- /dev/null +++ b/internal/wisski/ingredient/locker/locked.go @@ -0,0 +1,15 @@ +package locker + +import "github.com/FAU-CDI/wisski-distillery/internal/models" + +// Locked checks if this WissKI is currently locked. +func (lock *Locker) Locked() (locked bool) { + table, err := lock.SQL.QueryTable(true, models.LockTable) + if err != nil { + return false + } + + // check if this instance is locked + table.Select("count(*) > 0").Where("slug = ?", lock.Slug).Find(&locked) + return +} diff --git a/internal/wisski/ingredient/mstore/mstore.go b/internal/wisski/ingredient/mstore/mstore.go new file mode 100644 index 0000000..31e9440 --- /dev/null +++ b/internal/wisski/ingredient/mstore/mstore.go @@ -0,0 +1,43 @@ +package mstore + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/tkw1536/goprogram/lib/collection" +) + +// MStore implements metadata storage for this WissKI +type MStore struct { + ingredient.Base + *meta.Storage +} + +// For is a Store for the provided value +type For[Value any] meta.Key + +func (f For[Value]) Get(m *MStore) (value Value, err error) { + err = m.Storage.Get(meta.Key(f), &value) + return +} + +func (f For[Value]) GetAll(m *MStore) (values []Value, err error) { + err = m.Storage.GetAll(meta.Key(f), func(index, total int) any { + if values == nil { + values = make([]Value, total) + } + return &values[index] + }) + return values, err +} + +func (f For[Value]) Set(m *MStore, value Value) error { + return m.Storage.Set(meta.Key(f), value) +} + +func (f For[Value]) SetAll(m *MStore, values ...Value) error { + return m.Storage.SetAll(meta.Key(f), collection.AsAny(values)...) +} + +func (f For[Value]) Delete(m *MStore) error { + return m.Storage.Delete(meta.Key(f)) +} diff --git a/internal/wisski/ingredient/php/extras/pathbuilder.go b/internal/wisski/ingredient/php/extras/pathbuilder.go new file mode 100644 index 0000000..6f8615c --- /dev/null +++ b/internal/wisski/ingredient/php/extras/pathbuilder.go @@ -0,0 +1,44 @@ +package extras + +import ( + _ "embed" + + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" + "golang.org/x/exp/slices" +) + +type Pathbuilder struct { + ingredient.Base + + PHP *php.PHP +} + +//go:embed pathbuilder.php +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 *php.Server) (ids []string, err error) { + err = pathbuilder.PHP.ExecScript(server, &ids, pathbuilderPHP, "all_list") + slices.Sort(ids) + return +} + +// Get returns a single pathbuilder as xml. +// 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 *php.Server, id string) (xml string, err error) { + err = pathbuilder.PHP.ExecScript(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 *php.Server) (pathbuilders map[string]string, err error) { + err = pathbuilder.PHP.ExecScript(server, &pathbuilders, pathbuilderPHP, "all_xml") + return +} diff --git a/internal/wisski/php/export_pathbuilder.php b/internal/wisski/ingredient/php/extras/pathbuilder.php similarity index 100% rename from internal/wisski/php/export_pathbuilder.php rename to internal/wisski/ingredient/php/extras/pathbuilder.php diff --git a/internal/wisski/prefix.go b/internal/wisski/ingredient/php/extras/prefixes.go similarity index 57% rename from internal/wisski/prefix.go rename to internal/wisski/ingredient/php/extras/prefixes.go index bf7e3dd..94b8742 100644 --- a/internal/wisski/prefix.go +++ b/internal/wisski/ingredient/php/extras/prefixes.go @@ -1,47 +1,57 @@ -package wisski +package extras import ( "bufio" "path/filepath" "strings" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" "github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/tkw1536/goprogram/lib/collection" _ "embed" ) -// NoPrefix checks if this WissKI instance is excluded from generating prefixes. -// TODO: Move this to the database! -func (wisski *WissKI) NoPrefix() bool { - return fsx.IsFile(wisski.Core.Environment, filepath.Join(wisski.FilesystemBase, "prefixes.skip")) +// Prefixes implements reading and writing prefix +type Prefixes struct { + ingredient.Base + + PHP *php.PHP + MStore *mstore.MStore } -//go:embed php/list_uri_prefixes.php +// NoPrefix checks if this WissKI instance is excluded from generating prefixes. +// TODO: Move this to the database! +func (prefixes *Prefixes) NoPrefix() bool { + return fsx.IsFile(prefixes.Malt.Environment, filepath.Join(prefixes.FilesystemBase, "prefixes.skip")) +} + +//go:embed prefixes.php var listURIPrefixesPHP string -// Prefixes returns the prefixes applying to this WissKI +// All returns the prefixes applying to this WissKI // // server is an optional server to fetch prefixes from. // server may be nil. -func (wisski *WissKI) Prefixes(server *PHPServer) ([]string, error) { - prefixes, err := wisski.dbPrefixes(server) +func (prefixes *Prefixes) All(server *php.Server) ([]string, error) { + uris, err := prefixes.database(server) if err != nil { return nil, err } - prefixes2, err := wisski.filePrefixes() + uris2, err := prefixes.filePrefixes() if err != nil { return nil, err } - return append(prefixes, prefixes2...), nil + return append(uris, uris2...), nil } -func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err error) { +func (wisski *Prefixes) database(server *php.Server) (prefixes []string, err error) { // get all the ugly prefixes - err = wisski.ExecPHPScript(server, &prefixes, listURIPrefixesPHP, "list_prefixes") + err = wisski.PHP.ExecScript(server, &prefixes, listURIPrefixesPHP, "list_prefixes") if err != nil { return nil, err } @@ -52,7 +62,7 @@ func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err erro }) // load the list of blocked prefixes - blocks, err := wisski.blockedPrefixes() + blocks, err := wisski.blocked() if err != nil { return nil, err } @@ -61,9 +71,10 @@ func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err erro return collection.Filter(prefixes, func(uri string) bool { return !hasAnyPrefix(uri, blocks) }), nil } -func (wisski *WissKI) blockedPrefixes() ([]string, error) { +func (prefixes *Prefixes) blocked() ([]string, error) { // open the resolver block file - file, err := wisski.Core.Environment.Open(wisski.Core.Config.SelfResolverBlockFile) + // TODO: move this to the distillery + file, err := prefixes.Malt.Environment.Open(prefixes.Malt.Config.SelfResolverBlockFile) if err != nil { return nil, err } @@ -98,13 +109,13 @@ func hasAnyPrefix(candidate string, prefixes []string) bool { ) } -func (wisski *WissKI) filePrefixes() (prefixes []string, err error) { +func (wisski *Prefixes) filePrefixes() (prefixes []string, err error) { path := filepath.Join(wisski.FilesystemBase, "prefixes") - if !fsx.IsFile(wisski.Core.Environment, path) { + if !fsx.IsFile(wisski.Malt.Environment, path) { return nil, nil } - file, err := wisski.Core.Environment.Open(path) + file, err := wisski.Malt.Environment.Open(path) if err != nil { return nil, err } @@ -127,18 +138,18 @@ func (wisski *WissKI) filePrefixes() (prefixes []string, err error) { // CACHING -var prefix = meta.StorageFor[string]("prefix") +var prefix = mstore.For[string]("prefix") // Prefixes returns the cached prefixes from the given instance -func (wisski *WissKI) PrefixesCached() (results []string, err error) { - return prefix(wisski.storage()).GetAll() +func (wisski *Prefixes) PrefixesCached() (results []string, err error) { + return prefix.GetAll(wisski.MStore) } // UpdatePrefixes updates the cached prefixes of this instance -func (wisski *WissKI) UpdatePrefixes() error { - prefixes, err := wisski.Prefixes(nil) +func (wisski *Prefixes) UpdatePrefixes() error { + prefixes, err := wisski.All(nil) if err != nil { return err } - return prefix(wisski.storage()).SetAll(prefixes...) + return prefix.SetAll(wisski.MStore, prefixes...) } diff --git a/internal/wisski/php/list_uri_prefixes.php b/internal/wisski/ingredient/php/extras/prefixes.php similarity index 100% rename from internal/wisski/php/list_uri_prefixes.php rename to internal/wisski/ingredient/php/extras/prefixes.php diff --git a/internal/wisski/ingredient/php/extras/settings.go b/internal/wisski/ingredient/php/extras/settings.go new file mode 100644 index 0000000..f390690 --- /dev/null +++ b/internal/wisski/ingredient/php/extras/settings.go @@ -0,0 +1,26 @@ +package extras + +import ( + _ "embed" + + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" +) + +type Settings struct { + ingredient.Base + + PHP *php.PHP +} + +//go:embed settings.php +var settingsPHP string + +func (settings *Settings) Get(server *php.Server, key string) (value any, err error) { + err = settings.PHP.ExecScript(server, &value, settingsPHP, "get_setting", key) + return +} + +func (settings *Settings) Set(server *php.Server, key string, value any) error { + return settings.PHP.ExecScript(server, nil, settingsPHP, "set_setting", key, value) +} diff --git a/internal/wisski/php/settings.php b/internal/wisski/ingredient/php/extras/settings.php similarity index 100% rename from internal/wisski/php/settings.php rename to internal/wisski/ingredient/php/extras/settings.php diff --git a/internal/wisski/ingredient/php/php.go b/internal/wisski/ingredient/php/php.go new file mode 100644 index 0000000..37832cb --- /dev/null +++ b/internal/wisski/ingredient/php/php.go @@ -0,0 +1,58 @@ +package php + +import ( + "strings" + + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel" +) + +type PHP struct { + ingredient.Base + + Barrel *barrel.Barrel +} + +// ExecScript executes the PHP code as a script on the given server. +// When server is nil, creates a new server and automatically closes it after execution. +// Calling this function repeatedly with server = nil is inefficient. +// +// The script should define a function called entrypoint, and may define additional functions. +// +// Code must start with " no unmarshaling needed @@ -159,7 +159,7 @@ func (server *PHPServer) MarshalEval(value any, code string) error { } // Eval is like [MarshalEval], but returns the value as an any -func (server *PHPServer) Eval(code string) (value any, err error) { +func (server *Server) Eval(code string) (value any, err error) { err = server.MarshalEval(&value, code) return } @@ -168,18 +168,18 @@ func (server *PHPServer) 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 *PHPServer) MarshalCall(value any, function string, args ...any) error { +func (server *Server) MarshalCall(value any, function string, args ...any) error { // marshal a code for the call userFunction, err := marshalPHP(function) if err != nil { - return PHPServerError{Message: errPHPMarshal, Err: err} + return ServerError{Message: errPHPMarshal, Err: err} } userFunctionArgs := "[]" if len(args) > 0 { userFunctionArgs, err = marshalPHP(args) if err != nil { - return PHPServerError{Message: errPHPMarshal, Err: err} + return ServerError{Message: errPHPMarshal, Err: err} } } code := "return call_user_func_array(" + userFunction + "," + userFunctionArgs + ");" @@ -189,7 +189,7 @@ func (server *PHPServer) MarshalCall(value any, function string, args ...any) er } // Call is like [MarshalCall] but returns the return value of the function as an any -func (server *PHPServer) Call(function string, args ...any) (value any, err error) { +func (server *Server) Call(function string, args ...any) (value any, err error) { err = server.MarshalCall(&value, function, args...) return } @@ -233,7 +233,7 @@ func marshalPHP(data any) (string, error) { } // Close closes this server and prevents any further code from being run. -func (server *PHPServer) Close() error { +func (server *Server) Close() error { server.m.Lock() defer server.m.Unlock() @@ -248,51 +248,7 @@ func (server *PHPServer) Close() error { return nil } -// ExecPHPScript executes the PHP code as a script on the given server. -// When server is nil, creates a new server and automatically closes it after execution. -// Calling this function repeatedly with server = nil is inefficient. -// -// The script should define a function called entrypoint, and may define additional functions. -// -// Code must start with " 0").Where("slug = ?", wisski.Slug).Find(&locked) - return -} - -// Unlock unlocks this WissKI instance and returns if it succeeded -func (wisski WissKI) Unlock() bool { - table, err := wisski.SQL.QueryTable(true, models.LockTable) - if err != nil { - return false - } - result := table.Where("slug = ?", wisski.Slug).Delete(&models.Lock{}) - return result.Error == nil && result.RowsAffected == 1 -} diff --git a/internal/wisski/meta.go b/internal/wisski/meta.go deleted file mode 100644 index 83e103e..0000000 --- a/internal/wisski/meta.go +++ /dev/null @@ -1,7 +0,0 @@ -package wisski - -import "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" - -func (wisski *WissKI) storage() *meta.Storage { - return wisski.Meta.Storage(wisski.Slug) -} diff --git a/internal/wisski/pathbuilders.go b/internal/wisski/pathbuilders.go deleted file mode 100644 index 2c00e3c..0000000 --- a/internal/wisski/pathbuilders.go +++ /dev/null @@ -1,36 +0,0 @@ -package wisski - -import ( - _ "embed" - - "golang.org/x/exp/slices" -) - -//go:embed php/export_pathbuilder.php -var exportPathbuilderPHP string - -// Pathbuilders returns the ids of all pathbuilders in consistent order. -// -// server is the server to fetch the pathbuilders from, any may be nil. -func (wisski *WissKI) Pathbuilders(server *PHPServer) (ids []string, err error) { - err = wisski.ExecPHPScript(server, &ids, exportPathbuilderPHP, "all_list") - slices.Sort(ids) - return -} - -// Pathbuilder returns a single pathbuilder as xml. -// 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 (wisski *WissKI) Pathbuilder(server *PHPServer, id string) (xml string, err error) { - err = wisski.ExecPHPScript(server, &xml, exportPathbuilderPHP, "one_xml", id) - return -} - -// AllPathbuilders returns all pathbuilders serialized as xml -// -// server is the server to fetch the pathbuilders from, any may be nil. -func (wisski *WissKI) AllPathbuilders(server *PHPServer) (pathbuilders map[string]string, err error) { - err = wisski.ExecPHPScript(server, &pathbuilders, exportPathbuilderPHP, "all_xml") - return -} diff --git a/internal/wisski/php.go b/internal/wisski/php.go deleted file mode 100644 index c4c086f..0000000 --- a/internal/wisski/php.go +++ /dev/null @@ -1,17 +0,0 @@ -package wisski - -import ( - _ "embed" -) - -//go:embed php/settings.php -var settingsPHP string - -func (wisski *WissKI) GetSettingsPHP(server *PHPServer, key string) (value any, err error) { - err = wisski.ExecPHPScript(server, &value, settingsPHP, "get_setting", key) - return -} - -func (wisski *WissKI) SetSettingsPHP(server *PHPServer, key string, value any) error { - return wisski.ExecPHPScript(server, nil, settingsPHP, "set_setting", key, value) -} diff --git a/internal/wisski/provision.go b/internal/wisski/provision.go deleted file mode 100644 index e8975a6..0000000 --- a/internal/wisski/provision.go +++ /dev/null @@ -1,55 +0,0 @@ -package wisski - -import ( - "errors" - "strings" - - "github.com/alessio/shellescape" - "github.com/tkw1536/goprogram/stream" -) - -// Provision provisions an instance, assuming that the required databases already exist. -func (wisski *WissKI) Provision(io stream.IOStream) error { - - // build the container - if err := wisski.Build(io, false); err != nil { - return err - } - - provisionParams := []string{ - wisski.Domain(), - - wisski.SqlDatabase, - wisski.SqlUsername, - wisski.SqlPassword, - - wisski.GraphDBRepository, - wisski.GraphDBUsername, - wisski.GraphDBPassword, - - wisski.DrupalUsername, - wisski.DrupalPassword, - - "", // TODO: DrupalVersion - "", // TODO: WissKIVersion - } - - // escape the parameter - for i, param := range provisionParams { - provisionParams[i] = shellescape.Quote(param) - } - - // figure out the provision script - // 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 := wisski.Barrel().Run(io, true, "barrel", "/bin/bash", "-c", provisionScript) - if err != nil { - return err - } - if code != 0 { - return errors.New("unable to run provision script") - } - - return nil -} diff --git a/internal/wisski/snapshots.go b/internal/wisski/snapshots.go deleted file mode 100644 index bc58250..0000000 --- a/internal/wisski/snapshots.go +++ /dev/null @@ -1,8 +0,0 @@ -package wisski - -import "github.com/FAU-CDI/wisski-distillery/internal/models" - -// Snapshots returns the list of snapshots of this WissKI -func (wisski *WissKI) Snapshots() (snapshots []models.Export, err error) { - return wisski.ExporterLog.For(wisski.Slug) -} diff --git a/internal/wisski/stack.go b/internal/wisski/stack.go deleted file mode 100644 index 863bfd6..0000000 --- a/internal/wisski/stack.go +++ /dev/null @@ -1,127 +0,0 @@ -package wisski - -import ( - "embed" - "path/filepath" - "time" - - "github.com/FAU-CDI/wisski-distillery/internal/dis/component" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" - "github.com/tkw1536/goprogram/stream" -) - -//go:embed all:instances/barrel instances/barrel.env -var barrelResources embed.FS - -// Barrel returns a stack representing the running WissKI Instance -func (wisski *WissKI) Barrel() component.StackWithResources { - return component.StackWithResources{ - Stack: component.Stack{ - Dir: wisski.FilesystemBase, - Env: wisski.Core.Environment, - }, - - Resources: barrelResources, - ContextPath: filepath.Join("instances", "barrel"), - EnvPath: filepath.Join("instances", "barrel.env"), - - EnvContext: map[string]string{ - "DOCKER_NETWORK_NAME": wisski.Core.Config.DockerNetworkName, - - "SLUG": wisski.Slug, - "VIRTUAL_HOST": wisski.Domain(), - "HTTPS_ENABLED": wisski.Core.Config.HTTPSEnabledEnv(), - - "DATA_PATH": filepath.Join(wisski.FilesystemBase, "data"), - "RUNTIME_DIR": wisski.Core.Config.RuntimeDir(), - "GLOBAL_AUTHORIZED_KEYS_FILE": wisski.Core.Config.GlobalAuthorizedKeysFile, - }, - - MakeDirs: []string{"data", ".composer"}, - - TouchFiles: []string{ - filepath.Join("data", "authorized_keys"), - }, - } -} - -// TODO: Move this to time.Time -var lastRebuild = meta.StorageFor[int64]("lastRebuild") - -func (wisski *WissKI) LastRebuild() (t time.Time, err error) { - epoch, err := lastRebuild(wisski.storage()).Get() - if err == meta.ErrMetadatumNotSet { - return t, nil - } - if err != nil { - return t, err - } - - // and turn it into time! - return time.Unix(epoch, 0), nil -} - -func (wisski *WissKI) setLastRebuild() error { - return lastRebuild(wisski.storage()).Set(time.Now().Unix()) -} - -// Build builds or rebuilds the barel connected to this instance. -// -// It also logs the current time into the metadata belonging to this instance. -func (wisski *WissKI) Build(stream stream.IOStream, start bool) error { - if err := wisski.TryLock(); err != nil { - return err - } - defer wisski.Unlock() - - barrel := wisski.Barrel() - - var context component.InstallationContext - - { - err := barrel.Install(stream, context) - if err != nil { - return err - } - } - - { - err := barrel.Update(stream, start) - if err != nil { - return err - } - } - - // store the current last rebuild - return wisski.setLastRebuild() -} - -//go:embed all:instances/reserve instances/reserve.env -var reserveResources embed.FS - -// Reserve returns a stack representing the reserve instance -func (wisski *WissKI) Reserve() component.StackWithResources { - return component.StackWithResources{ - Stack: component.Stack{ - Dir: wisski.FilesystemBase, - Env: wisski.Core.Environment, - }, - - Resources: reserveResources, - ContextPath: filepath.Join("instances", "reserve"), - EnvPath: filepath.Join("instances", "reserve.env"), - - EnvContext: map[string]string{ - "DOCKER_NETWORK_NAME": wisski.Core.Config.DockerNetworkName, - - "SLUG": wisski.Slug, - "VIRTUAL_HOST": wisski.Domain(), - "HTTPS_ENABLED": wisski.Core.Config.HTTPSEnabledEnv(), - }, - } -} - -// Shell executes a shell command inside the instance. -func (wisski *WissKI) Shell(io stream.IOStream, argv ...string) (int, error) { - return wisski.Barrel().Exec(io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...) -} diff --git a/internal/wisski/update.go b/internal/wisski/update.go deleted file mode 100644 index efed605..0000000 --- a/internal/wisski/update.go +++ /dev/null @@ -1,47 +0,0 @@ -package wisski - -import ( - "time" - - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" - "github.com/FAU-CDI/wisski-distillery/pkg/environment" - "github.com/tkw1536/goprogram/exit" - "github.com/tkw1536/goprogram/stream" -) - -var errBlindUpdateFailed = exit.Error{ - Message: "Failed to run blind update script for instance %q: exited with code %s", - ExitCode: exit.ExitGeneric, -} - -// BlinUpdate performs a blind update of the given instance -func (wisski *WissKI) BlindUpdate(io stream.IOStream) error { - code, err := wisski.Shell(io, "/runtime/blind_update.sh") - if err != nil { - return errBlindUpdateFailed.WithMessageF(wisski.Slug, environment.ExecCommandError) - } - if code != 0 { - return errBlindUpdateFailed.WithMessageF(wisski.Slug, code) - } - - return wisski.setLastUpdate() -} - -var lastUpdate = meta.StorageFor[int64]("lastUpdate") - -func (wisski *WissKI) LastUpdate() (t time.Time, err error) { - epoch, err := lastUpdate(wisski.storage()).Get() - if err == meta.ErrMetadatumNotSet { - return t, nil - } - if err != nil { - return t, err - } - - // and turn it into time! - return time.Unix(epoch, 0), nil -} - -func (wisski *WissKI) setLastUpdate() error { - return lastUpdate(wisski.storage()).Set(time.Now().Unix()) -} diff --git a/internal/wisski/wisski.go b/internal/wisski/wisski.go index 28c3aa0..8d8ccc8 100644 --- a/internal/wisski/wisski.go +++ b/internal/wisski/wisski.go @@ -2,60 +2,108 @@ package wisski import ( - "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "sync" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore" - "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/drush" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/provisioner" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/bookkeeping" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/extras" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/reserve" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/liquid" + "github.com/FAU-CDI/wisski-distillery/pkg/lazy" ) -// WissKI represents a single WissKI Instance +// WissKI represents a single WissKI Instance. +// A WissKI may not be copied type WissKI struct { - models.Instance // whatever is stored inside the underlying instance + liquid.Liquid - // Drupal credentials - not stored in the database - DrupalUsername string - DrupalPassword string - - // references to components! - Core component.Still - Meta *meta.Meta - TS *triplestore.Triplestore - SQL *sql.SQL - - ExporterLog *logger.Logger + poolInit sync.Once + pool lazy.Pool[ingredient.Ingredient, *liquid.Liquid] } -// Save saves this instance in the bookkeeping table -func (wisski *WissKI) Save() error { - db, err := wisski.SQL.QueryTable(false, models.InstanceTable) - if err != nil { - return err - } +// +// PUBLIC INGREDIENT GETTERS +// - // it has never been created => we need to create it in the database - if wisski.Instance.Created.IsZero() { - return db.Create(&wisski.Instance).Error - } - - // Update based on the primary key! - return db.Where("pk = ?", wisski.Instance.Pk).Updates(&wisski.Instance).Error +func (wisski *WissKI) Locker() *locker.Locker { + return export[*locker.Locker](wisski) } -// Delete deletes this instance from the bookkeeping table -func (wisski *WissKI) Delete() error { - db, err := wisski.SQL.QueryTable(false, models.InstanceTable) - if err != nil { - return err - } - - // doesn't exist => nothing to delete - if wisski.Instance.Created.IsZero() { - return nil - } - - // delete it directly - return db.Delete(&wisski.Instance).Error +func (wisski *WissKI) Reserve() *reserve.Reserve { + return export[*reserve.Reserve](wisski) +} + +func (wisski *WissKI) Barrel() *barrel.Barrel { + return export[*barrel.Barrel](wisski) +} + +func (wisski *WissKI) Provisioner() *provisioner.Provisioner { + return export[*provisioner.Provisioner](wisski) +} + +func (wisski *WissKI) PHP() *php.PHP { + return export[*php.PHP](wisski) +} + +func (wisski *WissKI) Bookkeeping() *bookkeeping.Bookkeeping { + return export[*bookkeeping.Bookkeeping](wisski) +} + +func (wisski *WissKI) Drush() *drush.Drush { + return export[*drush.Drush](wisski) +} + +func (wisski *WissKI) Prefixes() *extras.Prefixes { + return export[*extras.Prefixes](wisski) +} + +func (wisski *WissKI) Settings() *extras.Settings { + return export[*extras.Settings](wisski) +} + +func (wisski *WissKI) Pathbuilder() *extras.Pathbuilder { + return export[*extras.Pathbuilder](wisski) +} + +func (wisski *WissKI) Info() *info.Info { + return export[*info.Info](wisski) +} + +// +// All components +// THESE SHOULD NEVER BE CALLED DIRECTLY +// + +func (wisski *WissKI) allIngredients() []initFunc { + return []initFunc{ + // core bits + auto[*locker.Locker], + manual(func(m *mstore.MStore) { + m.Storage = wisski.Malt.Meta.Storage(wisski.Slug) + }), + + // php + auto[*php.PHP], + auto[*extras.Prefixes], + auto[*extras.Settings], + auto[*extras.Pathbuilder], + + // info + auto[*info.Info], + + // stacks + auto[*barrel.Barrel], + auto[*bookkeeping.Bookkeeping], + auto[*provisioner.Provisioner], + auto[*drush.Drush], + + auto[*reserve.Reserve], + } }