From 063f3f9b7dc7a29478684da90336cd3b6bae3edf Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Mon, 17 Oct 2022 14:20:15 +0200 Subject: [PATCH] Move wisski instance code to separate package --- cmd/backup.go | 4 +- cmd/blind_update.go | 8 +- cmd/cron.go | 6 +- cmd/purge.go | 5 - cmd/rebuild.go | 6 +- cmd/snapshot.go | 4 +- cmd/system_update.go | 4 +- cmd/update_prefix_config.go | 6 +- internal/component/home/public.go | 6 +- internal/component/info/index.go | 8 +- internal/component/info/info.go | 2 + internal/component/info/instance.go | 3 +- internal/component/info/socket.go | 14 +- internal/component/instances/create.go | 65 +++++++++ internal/component/instances/instances.go | 69 +++++---- internal/component/instances/wisski_create.go | 111 --------------- internal/component/instances/wisski_db.go | 48 ------- internal/component/instances/wisski_exec.go | 10 -- internal/component/meta/meta.go | 44 ++++++ internal/component/meta/provision.go | 14 ++ .../{instances/meta.go => meta/storage.go} | 134 ++++++++++-------- internal/component/snapshots/backup.go | 12 +- internal/component/snapshots/iface.go | 10 +- internal/component/snapshots/manager.go | 6 +- internal/component/snapshots/prune.go | 2 +- internal/component/snapshots/snapshot.go | 6 +- .../snapshotslog.go} | 36 +++-- internal/dis/component.go | 59 ++++---- internal/dis/distillery.go | 2 +- .../wisski_cron.go => wisski/cron.go} | 2 +- .../wisski_info.go => wisski/info.go} | 2 +- .../instances => wisski}/instances/barrel.env | 0 .../instances/barrel/.dockerignore | 0 .../instances/barrel/Dockerfile | 0 .../instances/barrel/conf/ports.conf | 0 .../instances/barrel/conf/wisski.conf | 0 .../instances/barrel/conf/wisski.ini | 0 .../instances/barrel/docker-compose.yml | 0 .../instances/barrel/patch/easyrdf.patch | 0 .../instances/barrel/patch/triples.patch | 0 .../instances/barrel/scripts/entrypoint.sh | 0 .../barrel/scripts/provision_container.sh | 0 .../instances/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 .../instances/reserve.env | 0 .../instances/reserve/docker-compose.yml | 0 .../instances/reserve/index.html | 0 .../wisski_lock.go => wisski/lock.go} | 12 +- internal/wisski/meta.go | 7 + .../pathbuilders.go} | 2 +- .../instances/wisski_php.go => wisski/php.go} | 2 +- .../php/export_pathbuilder.php | 0 .../php/list_uri_prefixes.php | 0 .../instances => wisski}/php/server.php | 0 .../instances => wisski}/php/settings.php | 0 .../php_server.go} | 2 +- .../wisski_prefix.go => wisski/prefix.go} | 28 ++-- .../wisski_props.go => wisski/props.go} | 6 +- internal/wisski/provision.go | 55 +++++++ internal/wisski/snapshots.go | 8 ++ .../wisski_stack.go => wisski/stack.go} | 36 ++--- .../wisski_update.go => wisski/update.go} | 14 +- internal/wisski/wisski.go | 60 ++++++++ pkg/lazy/pool.go | 2 +- 67 files changed, 533 insertions(+), 409 deletions(-) create mode 100644 internal/component/instances/create.go delete mode 100644 internal/component/instances/wisski_create.go delete mode 100644 internal/component/instances/wisski_db.go delete mode 100644 internal/component/instances/wisski_exec.go create mode 100644 internal/component/meta/meta.go create mode 100644 internal/component/meta/provision.go rename internal/component/{instances/meta.go => meta/storage.go} (51%) rename internal/component/{instances/wisski_log.go => snapshotslog/snapshotslog.go} (59%) rename internal/{component/instances/wisski_cron.go => wisski/cron.go} (97%) rename internal/{component/instances/wisski_info.go => wisski/info.go} (99%) rename internal/{component/instances => wisski}/instances/barrel.env (100%) rename internal/{component/instances => wisski}/instances/barrel/.dockerignore (100%) rename internal/{component/instances => wisski}/instances/barrel/Dockerfile (100%) rename internal/{component/instances => wisski}/instances/barrel/conf/ports.conf (100%) rename internal/{component/instances => wisski}/instances/barrel/conf/wisski.conf (100%) rename internal/{component/instances => wisski}/instances/barrel/conf/wisski.ini (100%) rename internal/{component/instances => wisski}/instances/barrel/docker-compose.yml (100%) rename internal/{component/instances => wisski}/instances/barrel/patch/easyrdf.patch (100%) rename internal/{component/instances => wisski}/instances/barrel/patch/triples.patch (100%) rename internal/{component/instances => wisski}/instances/barrel/scripts/entrypoint.sh (100%) rename internal/{component/instances => wisski}/instances/barrel/scripts/provision_container.sh (100%) rename internal/{component/instances => wisski}/instances/barrel/scripts/user_shell.sh (100%) rename internal/{component/instances => wisski}/instances/barrel/wisskiutils/create_adapter.php (100%) rename internal/{component/instances => wisski}/instances/barrel/wisskiutils/set_trusted_host.sh (100%) rename internal/{component/instances => wisski}/instances/barrel/wisskiutils/settings_php_get.sh (100%) rename internal/{component/instances => wisski}/instances/barrel/wisskiutils/settings_php_set.sh (100%) rename internal/{component/instances => wisski}/instances/reserve.env (100%) rename internal/{component/instances => wisski}/instances/reserve/docker-compose.yml (100%) rename internal/{component/instances => wisski}/instances/reserve/index.html (100%) rename internal/{component/instances/wisski_lock.go => wisski/lock.go} (73%) create mode 100644 internal/wisski/meta.go rename internal/{component/instances/wisski_pathbuilders.go => wisski/pathbuilders.go} (98%) rename internal/{component/instances/wisski_php.go => wisski/php.go} (95%) rename internal/{component/instances => wisski}/php/export_pathbuilder.php (100%) rename internal/{component/instances => wisski}/php/list_uri_prefixes.php (100%) rename internal/{component/instances => wisski}/php/server.php (100%) rename internal/{component/instances => wisski}/php/settings.php (100%) rename internal/{component/instances/wisski_php_server.go => wisski/php_server.go} (99%) rename internal/{component/instances/wisski_prefix.go => wisski/prefix.go} (80%) rename internal/{component/instances/wisski_props.go => wisski/props.go} (75%) create mode 100644 internal/wisski/provision.go create mode 100644 internal/wisski/snapshots.go rename internal/{component/instances/wisski_stack.go => wisski/stack.go} (69%) rename internal/{component/instances/wisski_update.go => wisski/update.go} (77%) create mode 100644 internal/wisski/wisski.go diff --git a/cmd/backup.go b/cmd/backup.go index fd8a5fa..1df64cd 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -41,12 +41,12 @@ func (bk backup) Run(context wisski_distillery.Context) error { // prune old backups if !bk.NoPrune { defer logging.LogOperation(func() error { - return dis.SnapshotManager().PruneExports(context.IOStream) + return dis.ExportManager().PruneExports(context.IOStream) }, context.IOStream, "Pruning old backups") } // do the handling - err := dis.SnapshotManager().MakeExport(context.IOStream, snapshots.ExportTask{ + err := dis.ExportManager().MakeExport(context.IOStream, snapshots.ExportTask{ Dest: bk.Positionals.Dest, StagingOnly: bk.StagingOnly, diff --git a/cmd/blind_update.go b/cmd/blind_update.go index 62e7695..d1a1415 100644 --- a/cmd/blind_update.go +++ b/cmd/blind_update.go @@ -4,8 +4,8 @@ import ( "fmt" wisski_distillery "github.com/FAU-CDI/wisski-distillery" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/core" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/status" @@ -45,15 +45,15 @@ func (bu blindUpdate) Run(context wisski_distillery.Context) error { return err } if !bu.Force { - wissKIs = collection.Filter(wissKIs, func(instance instances.WissKI) bool { + wissKIs = collection.Filter(wissKIs, func(instance *wisski.WissKI) bool { return bool(instance.AutoBlindUpdateEnabled) }) } // and do the actual blind_update! - return status.StreamGroup(context.IOStream, bu.Parallel, func(instance instances.WissKI, str stream.IOStream) error { + return status.StreamGroup(context.IOStream, bu.Parallel, func(instance *wisski.WissKI, str stream.IOStream) error { return instance.BlindUpdate(str) - }, wissKIs, status.SmartMessage(func(item instances.WissKI) string { + }, 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 90a80d4..a512a0c 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -4,8 +4,8 @@ import ( "fmt" wisski_distillery "github.com/FAU-CDI/wisski-distillery" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/core" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/status" "github.com/tkw1536/goprogram/stream" ) @@ -39,9 +39,9 @@ func (cr cron) Run(context wisski_distillery.Context) error { } // and do the actual blind_update! - return status.StreamGroup(context.IOStream, cr.Parallel, func(instance instances.WissKI, io stream.IOStream) error { + return status.StreamGroup(context.IOStream, cr.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { return instance.Cron(io) - }, wissKIs, status.SmartMessage(func(item instances.WissKI) string { + }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("cron %q", item.Slug) })) } diff --git a/cmd/purge.go b/cmd/purge.go index 6989b49..c69a259 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -97,11 +97,6 @@ func (p purge) Run(context wisski_distillery.Context) error { context.EPrintln(err) } - logging.LogMessage(context.IOStream, "Purging instance metadata") - if err := instance.Metadata().Purge(); err != nil { - context.EPrintln(err) - } - // remove the filesystem logging.LogMessage(context.IOStream, "Remove lock data", instance.FilesystemBase) if !instance.Unlock() { diff --git a/cmd/rebuild.go b/cmd/rebuild.go index f3de6c5..34778e5 100644 --- a/cmd/rebuild.go +++ b/cmd/rebuild.go @@ -4,8 +4,8 @@ import ( "fmt" wisski_distillery "github.com/FAU-CDI/wisski-distillery" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/core" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/status" "github.com/tkw1536/goprogram/stream" @@ -46,9 +46,9 @@ func (rb rebuild) Run(context wisski_distillery.Context) error { } // and do the actual rebuild - return status.StreamGroup(context.IOStream, rb.Parallel, func(instance instances.WissKI, io stream.IOStream) error { + return status.StreamGroup(context.IOStream, rb.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { return instance.Build(io, true) - }, wissKIs, status.SmartMessage(func(item instances.WissKI) string { + }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("rebuild %q", item.Slug) })) } diff --git a/cmd/snapshot.go b/cmd/snapshot.go index b389a99..256556b 100644 --- a/cmd/snapshot.go +++ b/cmd/snapshot.go @@ -45,11 +45,11 @@ func (sn snapshot) Run(context wisski_distillery.Context) error { } // do a snapshot of it! - err = dis.SnapshotManager().MakeExport(context.IOStream, snapshots.ExportTask{ + err = dis.ExportManager().MakeExport(context.IOStream, snapshots.ExportTask{ Dest: sn.Positionals.Dest, StagingOnly: sn.StagingOnly, - Instance: &instance, + Instance: instance, }) if err != nil { diff --git a/cmd/system_update.go b/cmd/system_update.go index 057cef0..7beab85 100644 --- a/cmd/system_update.go +++ b/cmd/system_update.go @@ -70,8 +70,8 @@ func (si systemupdate) Run(context wisski_distillery.Context) error { for _, d := range []string{ dis.Config.DeployRoot, dis.Instances().Path(), - dis.SnapshotManager().StagingPath(), - dis.SnapshotManager().ArchivePath(), + dis.ExportManager().StagingPath(), + dis.ExportManager().ArchivePath(), } { context.Println(d) if err := dis.Core.Environment.MkdirAll(d, environment.DefaultDirPerm); err != nil { diff --git a/cmd/update_prefix_config.go b/cmd/update_prefix_config.go index fbf1f03..4b42df2 100644 --- a/cmd/update_prefix_config.go +++ b/cmd/update_prefix_config.go @@ -4,8 +4,8 @@ import ( "fmt" wisski_distillery "github.com/FAU-CDI/wisski-distillery" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/core" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/status" @@ -42,14 +42,14 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error { return errPrefixUpdateFailed.Wrap(err) } - return status.StreamGroup(context.IOStream, upc.Parallel, func(instance instances.WissKI, io stream.IOStream) error { + return status.StreamGroup(context.IOStream, upc.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error { io.Println("reading prefixes") err := instance.UpdatePrefixes() if err != nil { return errPrefixUpdateFailed.Wrap(err) } return nil - }, wissKIs, status.SmartMessage(func(item instances.WissKI) string { + }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("update_prefix %q", item.Slug) })) } diff --git a/internal/component/home/public.go b/internal/component/home/public.go index 50af427..6e3b093 100644 --- a/internal/component/home/public.go +++ b/internal/component/home/public.go @@ -7,8 +7,8 @@ import ( _ "embed" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/static" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "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([]instances.WissKIInfo, len(wissKIs)) + context.Instances = make([]wisski.WissKIInfo, len(wissKIs)) // determine their infos var eg errgroup.Group @@ -86,7 +86,7 @@ func (home *Home) homeRender() ([]byte, error) { } type HomeContext struct { - Instances []instances.WissKIInfo + Instances []wisski.WissKIInfo Time time.Time diff --git a/internal/component/info/index.go b/internal/component/info/index.go index 65b2906..6e7de2e 100644 --- a/internal/component/info/index.go +++ b/internal/component/info/index.go @@ -6,10 +6,10 @@ import ( _ "embed" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/static" "github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "golang.org/x/sync/errgroup" ) @@ -22,7 +22,7 @@ type indexPageContext struct { Config *config.Config - Instances []instances.WissKIInfo + Instances []wisski.WissKIInfo TotalCount int RunningCount int @@ -42,7 +42,7 @@ func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error } // get all of their info! - idx.Instances = make([]instances.WissKIInfo, len(all)) + idx.Instances = make([]wisski.WissKIInfo, len(all)) for i, instance := range all { { i := i @@ -61,7 +61,7 @@ func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error // get the log entries group.Go(func() (err error) { - idx.Backups, err = info.Instances.ExportLogFor("") + idx.Backups, err = info.SnapshotsLog.For("") return }) diff --git a/internal/component/info/info.go b/internal/component/info/info.go index 515321c..6cac7e6 100644 --- a/internal/component/info/info.go +++ b/internal/component/info/info.go @@ -7,6 +7,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/snapshots" + "github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/tkw1536/goprogram/stream" ) @@ -16,6 +17,7 @@ type Info struct { SnapshotManager *snapshots.Manager Instances *instances.Instances + SnapshotsLog *snapshotslog.SnapshotsLog } func (Info) Name() string { return "control-info" } diff --git a/internal/component/info/instance.go b/internal/component/info/instance.go index 3afc17d..0547793 100644 --- a/internal/component/info/instance.go +++ b/internal/component/info/instance.go @@ -9,6 +9,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/static" "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" ) @@ -20,7 +21,7 @@ type instancePageContext struct { Time time.Time Instance models.Instance - Info instances.WissKIInfo + Info wisski.WissKIInfo } func (info *Info) instancePageAPI(r *http.Request) (is instancePageContext, err error) { diff --git a/internal/component/info/socket.go b/internal/component/info/socket.go index 21a1252..a8356f0 100644 --- a/internal/component/info/socket.go +++ b/internal/component/info/socket.go @@ -1,34 +1,34 @@ package info import ( - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/snapshots" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/tkw1536/goprogram/status" "github.com/tkw1536/goprogram/stream" ) -type instanceActionFunc = func(info *Info, instance instances.WissKI, str stream.IOStream) error +type instanceActionFunc = func(info *Info, instance *wisski.WissKI, str stream.IOStream) error var socketInstanceActions = map[string]instanceActionFunc{ - "snapshot": func(info *Info, instance instances.WissKI, str stream.IOStream) error { + "snapshot": func(info *Info, instance *wisski.WissKI, str stream.IOStream) error { return info.SnapshotManager.MakeExport( str, snapshots.ExportTask{ Dest: "", - Instance: &instance, + Instance: instance, StagingOnly: false, }, ) }, - "rebuild": func(_ *Info, instance instances.WissKI, str stream.IOStream) error { + "rebuild": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error { return instance.Build(str, true) }, - "update": func(_ *Info, instance instances.WissKI, str stream.IOStream) error { + "update": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error { return instance.BlindUpdate(str) }, - "cron": func(_ *Info, instance instances.WissKI, str stream.IOStream) error { + "cron": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error { return instance.Cron(str) }, } diff --git a/internal/component/instances/create.go b/internal/component/instances/create.go new file mode 100644 index 0000000..70f2385 --- /dev/null +++ b/internal/component/instances/create.go @@ -0,0 +1,65 @@ +package instances + +import ( + "errors" + "path/filepath" + + "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/pkg/stringparser" +) + +var errInvalidSlug = errors.New("not a valid slug") + +// Create fills the struct for a new WissKI instance. +// It validates that slug is a valid name for an instance. +// +// It does not perform any checks if the instance already exists, or does the creation in the database. +func (instances *Instances) Create(slug string) (wissKI *wisski.WissKI, err error) { + + // make sure that the slug is valid! + slug, err = stringparser.ParseSlug(instances.Environment, slug) + if err != nil { + return nil, errInvalidSlug + } + + wissKI = new(wisski.WissKI) + instances.use(wissKI) + + wissKI.Instance.Slug = slug + wissKI.Instance.FilesystemBase = filepath.Join(instances.Path(), wissKI.Domain()) + + wissKI.Instance.OwnerEmail = "" + wissKI.Instance.AutoBlindUpdateEnabled = true + + // sql + + wissKI.Instance.SqlDatabase = instances.Config.MysqlDatabasePrefix + slug + wissKI.Instance.SqlUsername = instances.Config.MysqlUserPrefix + slug + + wissKI.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.Instance.GraphDBPassword, err = instances.Config.NewPassword() + if err != nil { + return nil, err + } + + // drupal + + wissKI.DrupalUsername = "admin" // TODO: Change this! + + wissKI.DrupalPassword, err = instances.Config.NewPassword() + if err != nil { + return nil, err + } + + // store the instance in the object and return it! + return wissKI, nil +} diff --git a/internal/component/instances/instances.go b/internal/component/instances/instances.go index 52c508d..d4a6d5a 100644 --- a/internal/component/instances/instances.go +++ b/internal/component/instances/instances.go @@ -5,9 +5,12 @@ import ( "path/filepath" "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog" "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/component/triplestore" "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/exit" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -17,8 +20,10 @@ import ( type Instances struct { component.ComponentBase - TS *triplestore.Triplestore - SQL *sql.SQL + TS *triplestore.Triplestore + SQL *sql.SQL + Meta *meta.Meta + SnapshotsLog *snapshotslog.SnapshotsLog } func (Instances) Name() string { @@ -37,40 +42,53 @@ var errSQL = exit.Error{ ExitCode: exit.ExitGeneric, } -// 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 { - i, err := instances.WissKI(instance.Slug) - if err != nil { - return nil - } - return &i +// use uses the non-nil wisski instance with this instances +func (instances *Instances) use(wisski *wisski.WissKI) { + wisski.Core = instances.Core + wisski.SQL = instances.SQL + wisski.TS = instances.TS + wisski.Meta = instances.Meta + wisski.SnapshotsLog = instances.SnapshotsLog } // 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) (i WissKI, err error) { +func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err error) { sql := instances.SQL if err := sql.WaitQueryTable(); err != nil { - return i, err + return nil, err } table, err := sql.QueryTable(false, models.InstanceTable) if err != nil { - return i, err + return nil, err } + // create a struct + wissKI = new(wisski.WissKI) + // find the instance by slug - query := table.Where(&models.Instance{Slug: slug}).Find(&i.Instance) + query := table.Where(&models.Instance{Slug: slug}).Find(&wissKI.Instance) switch { case query.Error != nil: - return i, errSQL.WithMessageF(query.Error) + return nil, errSQL.WithMessageF(query.Error) case query.RowsAffected == 0: - return i, ErrWissKINotFound - default: - i.instances = instances - return i, nil + return nil, ErrWissKINotFound } + + // use the wissKI instance + instances.use(wissKI) + return wissKI, nil +} + +// 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) + if err != nil { + return nil + } + return wissKI } // Has checks if a WissKI with the provided slug exists inside the database. @@ -96,7 +114,7 @@ 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, error) { +func (instances *Instances) All() ([]*wisski.WissKI, error) { return instances.find(true, func(table *gorm.DB) *gorm.DB { return table }) @@ -104,14 +122,14 @@ func (instances *Instances) All() ([]WissKI, error) { // 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, error) { +func (instances *Instances) WissKIs(slugs ...string) ([]*wisski.WissKI, error) { return instances.find(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, error) { +func (instances *Instances) Load(slugs ...string) ([]*wisski.WissKI, error) { if len(slugs) == 0 { return instances.All() } @@ -119,7 +137,7 @@ func (instances *Instances) Load(slugs ...string) ([]WissKI, error) { } // find finds instances based on the provided query -func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []WissKI, err error) { +func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) { sql := instances.SQL if err := sql.WaitQueryTable(); err != nil { return nil, err @@ -148,10 +166,11 @@ func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB } // make proper instances - results = make([]WissKI, len(bks)) + results = make([]*wisski.WissKI, len(bks)) for i, bk := range bks { + results[i] = new(wisski.WissKI) results[i].Instance = bk - results[i].instances = instances + instances.use(results[i]) } return results, nil diff --git a/internal/component/instances/wisski_create.go b/internal/component/instances/wisski_create.go deleted file mode 100644 index c8994b5..0000000 --- a/internal/component/instances/wisski_create.go +++ /dev/null @@ -1,111 +0,0 @@ -package instances - -import ( - "errors" - "path/filepath" - "strings" - - "github.com/FAU-CDI/wisski-distillery/pkg/stringparser" - "github.com/alessio/shellescape" - "github.com/tkw1536/goprogram/stream" -) - -var errInvalidSlug = errors.New("not a valid slug") - -// Create fills the struct for a new WissKI instance. -// It validates that slug is a valid name for an instance. -// -// It does not perform any checks if the instance already exists, or does the creation in the database. -func (instances *Instances) Create(slug string) (wisski WissKI, err error) { - wisski.instances = instances - - // make sure that the slug is valid! - slug, err = stringparser.ParseSlug(instances.Environment, slug) - if err != nil { - return wisski, errInvalidSlug - } - - wisski.Instance.Slug = slug - wisski.Instance.FilesystemBase = filepath.Join(instances.Path(), wisski.Domain()) - - wisski.Instance.OwnerEmail = "" - wisski.Instance.AutoBlindUpdateEnabled = true - - // sql - - wisski.Instance.SqlDatabase = instances.Config.MysqlDatabasePrefix + slug - wisski.Instance.SqlUsername = instances.Config.MysqlUserPrefix + slug - - wisski.Instance.SqlPassword, err = instances.Config.NewPassword() - if err != nil { - return WissKI{}, err - } - - // triplestore - - wisski.Instance.GraphDBRepository = instances.Config.GraphDBRepoPrefix + slug - wisski.Instance.GraphDBUsername = instances.Config.GraphDBUserPrefix + slug - - wisski.Instance.GraphDBPassword, err = instances.Config.NewPassword() - if err != nil { - return WissKI{}, err - } - - // drupal - - wisski.DrupalUsername = "admin" // TODO: Change this! - - wisski.DrupalPassword, err = instances.Config.NewPassword() - if err != nil { - return wisski, err - } - - // store the instance in the object and return it! - return wisski, nil -} - -// 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/component/instances/wisski_db.go b/internal/component/instances/wisski_db.go deleted file mode 100644 index 533bbf3..0000000 --- a/internal/component/instances/wisski_db.go +++ /dev/null @@ -1,48 +0,0 @@ -package instances - -import "github.com/FAU-CDI/wisski-distillery/internal/models" - -// WissKI represents a single WissKI Instance -type WissKI struct { - // Whatever is stored inside the bookkeeping database - models.Instance - - // Credentials to Drupal - DrupalUsername string - DrupalPassword string - - // reference to the component! - instances *Instances -} - -// Save saves this instance in the bookkeeping table -func (wisski *WissKI) Save() error { - db, err := wisski.instances.SQL.QueryTable(false, models.InstanceTable) - if err != nil { - return err - } - - // 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 -} - -// Delete deletes this instance from the bookkeeping table -func (wisski *WissKI) Delete() error { - db, err := wisski.instances.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 -} diff --git a/internal/component/instances/wisski_exec.go b/internal/component/instances/wisski_exec.go deleted file mode 100644 index d9da2cc..0000000 --- a/internal/component/instances/wisski_exec.go +++ /dev/null @@ -1,10 +0,0 @@ -package instances - -import ( - "github.com/tkw1536/goprogram/stream" -) - -// 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/component/meta/meta.go b/internal/component/meta/meta.go new file mode 100644 index 0000000..b43098a --- /dev/null +++ b/internal/component/meta/meta.go @@ -0,0 +1,44 @@ +package meta + +import ( + "sync" + + "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/sql" +) + +// Component meta is responsible for managing metadata per WissKI Instance +type Meta struct { + component.ComponentBase + + SQL *sql.SQL + + sl sync.Mutex + sc map[string]*Storage +} + +func (*Meta) Name() string { return "metadata" } + +// Storage returns a Storage for the instance with the given slug. +// When slug is nil, returns a global storage. +func (meta *Meta) Storage(slug string) *Storage { + meta.sl.Lock() + defer meta.sl.Unlock() + + // create the cache (unless it already exists) + if meta.sc == nil { + meta.sc = make(map[string]*Storage) + } + + // cache hit + if storage, ok := meta.sc[slug]; ok { + return storage + } + + // create a new storage + meta.sc[slug] = &Storage{ + Slug: slug, + sql: meta.SQL, + } + return meta.sc[slug] +} diff --git a/internal/component/meta/provision.go b/internal/component/meta/provision.go new file mode 100644 index 0000000..e05e304 --- /dev/null +++ b/internal/component/meta/provision.go @@ -0,0 +1,14 @@ +package meta + +import "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 { + 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() +} diff --git a/internal/component/instances/meta.go b/internal/component/meta/storage.go similarity index 51% rename from internal/component/instances/meta.go rename to internal/component/meta/storage.go index 2534316..be72821 100644 --- a/internal/component/instances/meta.go +++ b/internal/component/meta/storage.go @@ -1,4 +1,4 @@ -package instances +package meta import ( "encoding/json" @@ -6,68 +6,26 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/tkw1536/goprogram/lib/collection" "gorm.io/gorm" ) -// MetaKey represents a key for metadata. -type MetaKey string +// Key represents a key for metadata. +type Key string // ErrMetadatumNotSet is returned by various [MetaStorage] functions when a metadatum is not set var ErrMetadatumNotSet = errors.New("metadatum not set") -// MetaStorage manages some metadata. -type MetaStorage interface { - // Get retrieves metadata with the provided key and deserializes the first one into target. - // If no metadatum exists, returns [ErrMetadatumNotSet]. - Get(key MetaKey, target any) error - - // GetAll receives all metadata with the provided keys. - // For each received value, the targets function is called with the current index, and total number of results. - // The function is intended to return a target for deserialization. - // - // When no metadatum exists, targets is not called, and nil error is returned. - GetAll(key MetaKey, targets func(index, total int) any) error - - // Delete deletes all metadata with the provided key. - Delete(key MetaKey) error - - // Set serializes value and stores it with the provided key. - // Any other metadata with the same key is deleted. - Set(key MetaKey, value any) error - - // Set serializes values and stores them with the provided key. - // Any other metadata with the same key is deleted. - SetAll(key MetaKey, values ...any) error - - // Purge removes all metadata, regardless of key. - Purge() error -} - -// Metadata returns a system-wide [MetaStorage]. -func (instances *Instances) Metadata() MetaStorage { - return &storage{ - SQL: instances.SQL, - Slug: "", // not associated to any slug - } -} - -// Metadata returns a [MetaStorage] that manages metadata related to this WissKI instance. -// It will be automatically deleted once the instance is deleted. -func (wisski *WissKI) Metadata() MetaStorage { - return &storage{ - SQL: wisski.instances.SQL, - Slug: wisski.Slug, // associated to this instance - } -} - -// storage implements MetaStorage -type storage struct { - SQL *sql.SQL +// Storage manages metadata for either the entire distillery, or a single slug +type Storage struct { Slug string + sql *sql.SQL } -func (s *storage) Get(key MetaKey, target any) error { - table, err := s.SQL.QueryTable(true, models.MetadataTable) +// 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) if err != nil { return err } @@ -90,8 +48,13 @@ func (s *storage) Get(key MetaKey, target any) error { return json.Unmarshal(datum.Value, target) } -func (s *storage) GetAll(key MetaKey, target func(index, total int) any) error { - table, err := s.SQL.QueryTable(true, models.MetadataTable) +// GetAll receives all metadata with the provided keys. +// For each received value, the targets function is called with the current index, and total number of results. +// 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) if err != nil { return err } @@ -116,8 +79,9 @@ func (s *storage) GetAll(key MetaKey, target func(index, total int) any) error { return nil } -func (s *storage) Delete(key MetaKey) error { - table, err := s.SQL.QueryTable(true, models.MetadataTable) +// Delete deletes all metadata with the provided key. +func (s Storage) Delete(key Key) error { + table, err := s.sql.QueryTable(true, models.MetadataTable) if err != nil { return err } @@ -130,8 +94,10 @@ func (s *storage) Delete(key MetaKey) error { return nil } -func (s *storage) Set(key MetaKey, value any) error { - table, err := s.SQL.QueryTable(true, models.MetadataTable) +// 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) if err != nil { return err } @@ -163,8 +129,10 @@ func (s *storage) Set(key MetaKey, value any) error { }) } -func (s *storage) SetAll(key MetaKey, values ...any) error { - table, err := s.SQL.QueryTable(true, models.MetadataTable) +// 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) if err != nil { return err } @@ -196,8 +164,9 @@ func (s *storage) SetAll(key MetaKey, values ...any) error { }) } -func (s *storage) Purge() error { - table, err := s.SQL.QueryTable(true, models.MetadataTable) +// Purge removes all metadata, regardless of key. +func (s Storage) Purge() error { + table, err := s.sql.QueryTable(true, models.MetadataTable) if err != nil { return err } @@ -208,3 +177,42 @@ 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/component/snapshots/backup.go b/internal/component/snapshots/backup.go index 55a1faf..b94f97b 100644 --- a/internal/component/snapshots/backup.go +++ b/internal/component/snapshots/backup.go @@ -7,7 +7,7 @@ import ( "time" "github.com/FAU-CDI/wisski-distillery/internal/component" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/FAU-CDI/wisski-distillery/pkg/logging" @@ -131,13 +131,13 @@ func (backup *Backup) run(ios stream.IOStream, manager *Manager) { } // make a backup of the snapshots - backup.InstanceSnapshots = status.Group[instances.WissKI, Snapshot]{ - PrefixString: func(item instances.WissKI, index int) string { + backup.InstanceSnapshots = status.Group[*wisski.WissKI, Snapshot]{ + PrefixString: func(item *wisski.WissKI, index int) string { return fmt.Sprintf("[snapshot %q]: ", item.Slug) }, PrefixAlign: true, - Handler: func(instance instances.WissKI, index int, writer io.Writer) Snapshot { + Handler: func(instance *wisski.WissKI, index int, writer io.Writer) Snapshot { dir := filepath.Join(instancesBackupDir, instance.Slug) if err := manager.Environment.Mkdir(dir, environment.DefaultDirPerm); err != nil { return Snapshot{ @@ -151,10 +151,10 @@ func (backup *Backup) run(ios stream.IOStream, manager *Manager) { Dest: dir, }) }, - ResultString: func(res Snapshot, item instances.WissKI, index int) string { + ResultString: func(res Snapshot, item *wisski.WissKI, index int) string { return "done" }, - WaitString: status.DefaultWaitString[instances.WissKI], + WaitString: status.DefaultWaitString[*wisski.WissKI], HandlerLimit: backup.Description.ConcurrentSnapshots, }.Use(st, wissKIs) diff --git a/internal/component/snapshots/iface.go b/internal/component/snapshots/iface.go index 8accf3e..52ef9a6 100644 --- a/internal/component/snapshots/iface.go +++ b/internal/component/snapshots/iface.go @@ -4,8 +4,8 @@ import ( "io" "path/filepath" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/targz" @@ -26,7 +26,7 @@ type ExportTask struct { // Instance is the instance to generate a snapshot of. // To generate a backup, leave this to be nil. - Instance *instances.WissKI + Instance *wisski.WissKI // BackupDescriptions and SnapshotDescriptions further specitfy options for the export. // The Dest parameter is ignored, and updated automatically. @@ -99,7 +99,7 @@ func (manager *Manager) MakeExport(io stream.IOStream, task ExportTask) (err err sl = &backup } else { task.SnapshotDescription.Dest = stagingDir - snapshot := manager.NewSnapshot(*task.Instance, io, task.SnapshotDescription) + snapshot := manager.NewSnapshot(task.Instance, io, task.SnapshotDescription) sl = &snapshot } @@ -131,7 +131,7 @@ func (manager *Manager) MakeExport(io stream.IOStream, task ExportTask) (err err // write out the log entry entry.Path = stagingDir entry.Packed = false - manager.Instances.AddToExportLog(entry) + manager.SnapshotsLog.Add(entry) io.Printf("Wrote %s\n", stagingDir) return nil @@ -159,7 +159,7 @@ func (manager *Manager) MakeExport(io stream.IOStream, task ExportTask) (err err logging.LogMessage(io, "Writing Log Entry") entry.Path = archivePath entry.Packed = true - manager.Instances.AddToExportLog(entry) + manager.SnapshotsLog.Add(entry) // and we're done! return nil diff --git a/internal/component/snapshots/manager.go b/internal/component/snapshots/manager.go index 8d38fac..8a44140 100644 --- a/internal/component/snapshots/manager.go +++ b/internal/component/snapshots/manager.go @@ -7,6 +7,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog" "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/FAU-CDI/wisski-distillery/pkg/fsx" @@ -17,8 +18,9 @@ import ( type Manager struct { component.ComponentBase - SQL *sql.SQL - Instances *instances.Instances + SQL *sql.SQL + Instances *instances.Instances + SnapshotsLog *snapshotslog.SnapshotsLog Snapshotable []component.Snapshotable Backupable []component.Backupable diff --git a/internal/component/snapshots/prune.go b/internal/component/snapshots/prune.go index c99b53c..60abab7 100644 --- a/internal/component/snapshots/prune.go +++ b/internal/component/snapshots/prune.go @@ -50,6 +50,6 @@ func (manager *Manager) PruneExports(io stream.IOStream) error { } // prune the snapshot log! - _, err = manager.Instances.ExportLog() + _, err = manager.SnapshotsLog.Log() return err } diff --git a/internal/component/snapshots/snapshot.go b/internal/component/snapshots/snapshot.go index 83e9eaa..39cbc39 100644 --- a/internal/component/snapshots/snapshot.go +++ b/internal/component/snapshots/snapshot.go @@ -7,8 +7,8 @@ import ( "time" "github.com/FAU-CDI/wisski-distillery/internal/component" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/tkw1536/goprogram/lib/collection" "github.com/tkw1536/goprogram/status" @@ -43,7 +43,7 @@ type Snapshot struct { } // Snapshot creates a new snapshot of this instance into dest -func (snapshots *Manager) NewSnapshot(instance instances.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) { +func (snapshots *Manager) NewSnapshot(instance *wisski.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) { logging.LogMessage(io, "Locking instance") if err := instance.TryLock(); err != nil { @@ -83,7 +83,7 @@ func (snapshots *Manager) NewSnapshot(instance instances.WissKI, io stream.IOStr return } -func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Manager, instance instances.WissKI, needsRunning bool) map[string]error { +func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Manager, instance *wisski.WissKI, needsRunning bool) map[string]error { if !needsRunning && !snapshot.Description.Keepalive { stack := instance.Barrel() diff --git a/internal/component/instances/wisski_log.go b/internal/component/snapshotslog/snapshotslog.go similarity index 59% rename from internal/component/instances/wisski_log.go rename to internal/component/snapshotslog/snapshotslog.go index cd3b5ae..a01e753 100644 --- a/internal/component/instances/wisski_log.go +++ b/internal/component/snapshotslog/snapshotslog.go @@ -1,16 +1,27 @@ -package instances +package snapshotslog import ( + "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/tkw1536/goprogram/lib/collection" ) -// ExportLogFor retrieves (and prunes) the ExportLog. +// SnapshotsLog is responsible for logging snapshots +type SnapshotsLog struct { + component.ComponentBase + + SQL *sql.SQL +} + +func (*SnapshotsLog) Name() string { return "snapshots-log" } + +// For retrieves (and prunes) the ExportLog. // Slug determines if entries for Backups (empty slug) // or a specific Instance (non-empty slug) are returned. -func (instances *Instances) ExportLogFor(slug string) (exports []models.Export, err error) { - exports, err = instances.ExportLog() +func (log *SnapshotsLog) For(slug string) (exports []models.Export, err error) { + exports, err = log.Log() if err != nil { return nil, err } @@ -20,10 +31,10 @@ func (instances *Instances) ExportLogFor(slug string) (exports []models.Export, }), nil } -// ExportLog retrieves (and prunes) all entries in the snapshot log. -func (instances *Instances) ExportLog() ([]models.Export, error) { +// Log retrieves (and prunes) all entries in the snapshot log. +func (log *SnapshotsLog) Log() ([]models.Export, error) { // query the table! - table, err := instances.SQL.QueryTable(false, models.ExportTable) + table, err := log.SQL.QueryTable(false, models.ExportTable) if err != nil { return nil, err } @@ -37,7 +48,7 @@ func (instances *Instances) ExportLog() ([]models.Export, error) { // partition out the exports that have been deleted! parts := collection.Partition(exports, func(s models.Export) bool { - _, err := instances.Core.Environment.Stat(s.Path) + _, err := log.Core.Environment.Stat(s.Path) return !environment.IsNotExist(err) }) @@ -52,15 +63,10 @@ func (instances *Instances) ExportLog() ([]models.Export, error) { return parts[true], nil } -// Snapshots returns the list of snapshots of this WissKI -func (wisski *WissKI) Snapshots() (snapshots []models.Export, err error) { - return wisski.instances.ExportLogFor(wisski.Slug) -} - // AddToExportLog adds the provided export to the log. -func (instances *Instances) AddToExportLog(export models.Export) error { +func (log *SnapshotsLog) Add(export models.Export) error { // find the table - table, err := instances.SQL.QueryTable(false, models.ExportTable) + table, err := log.SQL.QueryTable(false, models.ExportTable) if err != nil { return err } diff --git a/internal/dis/component.go b/internal/dis/component.go index 81cd403..e7b1f4a 100644 --- a/internal/dis/component.go +++ b/internal/dis/component.go @@ -8,61 +8,72 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component/home" "github.com/FAU-CDI/wisski-distillery/internal/component/info" "github.com/FAU-CDI/wisski-distillery/internal/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/component/meta" "github.com/FAU-CDI/wisski-distillery/internal/component/resolver" "github.com/FAU-CDI/wisski-distillery/internal/component/snapshots" + "github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog" "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/component/ssh" "github.com/FAU-CDI/wisski-distillery/internal/component/static" "github.com/FAU-CDI/wisski-distillery/internal/component/triplestore" "github.com/FAU-CDI/wisski-distillery/internal/component/web" + "github.com/tkw1536/goprogram/lib/collection" ) // register returns all components of the distillery func (dis *Distillery) register(context component.ComponentPoolContext) []component.Component { - return []component.Component{ - ra[*web.Web](dis, context), + return collection.MapSlice([]initFunc{ + auto[*web.Web], - ra[*ssh.SSH](dis, context), + auto[*ssh.SSH], - r(dis, context, func(ts *triplestore.Triplestore) { + manual(func(ts *triplestore.Triplestore) { ts.BaseURL = "http://" + dis.Upstream.Triplestore ts.PollContext = dis.Context() ts.PollInterval = time.Second }), - r(dis, context, func(sql *sql.SQL) { + manual(func(sql *sql.SQL) { sql.ServerURL = dis.Upstream.SQL sql.PollContext = dis.Context() sql.PollInterval = time.Second }), - ra[*instances.Instances](dis, context), + auto[*instances.Instances], + auto[*meta.Meta], // Snapshots - ra[*snapshots.Manager](dis, context), - ra[*snapshots.Config](dis, context), - ra[*snapshots.Bookkeeping](dis, context), - ra[*snapshots.Filesystem](dis, context), - ra[*snapshots.Pathbuilders](dis, context), + auto[*snapshots.Manager], + auto[*snapshotslog.SnapshotsLog], + auto[*snapshots.Config], + auto[*snapshots.Bookkeeping], + auto[*snapshots.Filesystem], + auto[*snapshots.Pathbuilders], // Control server - ra[*control.Control](dis, context), - ra[*static.Static](dis, context), - r(dis, context, func(home *home.Home) { + auto[*control.Control], + auto[*static.Static], + manual(func(home *home.Home) { home.RefreshInterval = time.Minute }), - r(dis, context, func(resolver *resolver.Resolver) { + manual(func(resolver *resolver.Resolver) { resolver.RefreshInterval = time.Minute }), - ra[*info.Info](dis, context), + auto[*info.Info], + }, func(f initFunc) component.Component { + return f(dis, context) + }) +} + +type initFunc = func(dis *Distillery, context component.ComponentPoolContext) component.Component + +// manual initializes a component from the provided distillery. +func manual[C component.Component](init func(component C)) initFunc { + return func(dis *Distillery, context component.ComponentPoolContext) component.Component { + return component.MakeComponent(context, dis.Core, init) } } -// r initializes a component from the provided distillery. -func r[C component.Component](dis *Distillery, context component.ComponentPoolContext, init func(component C)) C { - return component.MakeComponent(context, dis.Core, init) -} - -// ra is like r, but does not provided additional initialization -func ra[C component.Component](dis *Distillery, context component.ComponentPoolContext) C { - return r[C](dis, context, nil) +// use is like r, but does not provided additional initialization +func auto[C component.Component](dis *Distillery, context component.ComponentPoolContext) component.Component { + return component.MakeComponent[C](context, dis.Core, nil) } diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index 38d2283..cda2c49 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -85,7 +85,7 @@ func (dis *Distillery) Triplestore() *triplestore.Triplestore { func (dis *Distillery) Instances() *instances.Instances { return e[*instances.Instances](dis) } -func (dis *Distillery) SnapshotManager() *snapshots.Manager { +func (dis *Distillery) ExportManager() *snapshots.Manager { return e[*snapshots.Manager](dis) } diff --git a/internal/component/instances/wisski_cron.go b/internal/wisski/cron.go similarity index 97% rename from internal/component/instances/wisski_cron.go rename to internal/wisski/cron.go index 31c4f2b..32fadf3 100644 --- a/internal/component/instances/wisski_cron.go +++ b/internal/wisski/cron.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( "time" diff --git a/internal/component/instances/wisski_info.go b/internal/wisski/info.go similarity index 99% rename from internal/component/instances/wisski_info.go rename to internal/wisski/info.go index c6968d4..132c42e 100644 --- a/internal/component/instances/wisski_info.go +++ b/internal/wisski/info.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( "time" diff --git a/internal/component/instances/instances/barrel.env b/internal/wisski/instances/barrel.env similarity index 100% rename from internal/component/instances/instances/barrel.env rename to internal/wisski/instances/barrel.env diff --git a/internal/component/instances/instances/barrel/.dockerignore b/internal/wisski/instances/barrel/.dockerignore similarity index 100% rename from internal/component/instances/instances/barrel/.dockerignore rename to internal/wisski/instances/barrel/.dockerignore diff --git a/internal/component/instances/instances/barrel/Dockerfile b/internal/wisski/instances/barrel/Dockerfile similarity index 100% rename from internal/component/instances/instances/barrel/Dockerfile rename to internal/wisski/instances/barrel/Dockerfile diff --git a/internal/component/instances/instances/barrel/conf/ports.conf b/internal/wisski/instances/barrel/conf/ports.conf similarity index 100% rename from internal/component/instances/instances/barrel/conf/ports.conf rename to internal/wisski/instances/barrel/conf/ports.conf diff --git a/internal/component/instances/instances/barrel/conf/wisski.conf b/internal/wisski/instances/barrel/conf/wisski.conf similarity index 100% rename from internal/component/instances/instances/barrel/conf/wisski.conf rename to internal/wisski/instances/barrel/conf/wisski.conf diff --git a/internal/component/instances/instances/barrel/conf/wisski.ini b/internal/wisski/instances/barrel/conf/wisski.ini similarity index 100% rename from internal/component/instances/instances/barrel/conf/wisski.ini rename to internal/wisski/instances/barrel/conf/wisski.ini diff --git a/internal/component/instances/instances/barrel/docker-compose.yml b/internal/wisski/instances/barrel/docker-compose.yml similarity index 100% rename from internal/component/instances/instances/barrel/docker-compose.yml rename to internal/wisski/instances/barrel/docker-compose.yml diff --git a/internal/component/instances/instances/barrel/patch/easyrdf.patch b/internal/wisski/instances/barrel/patch/easyrdf.patch similarity index 100% rename from internal/component/instances/instances/barrel/patch/easyrdf.patch rename to internal/wisski/instances/barrel/patch/easyrdf.patch diff --git a/internal/component/instances/instances/barrel/patch/triples.patch b/internal/wisski/instances/barrel/patch/triples.patch similarity index 100% rename from internal/component/instances/instances/barrel/patch/triples.patch rename to internal/wisski/instances/barrel/patch/triples.patch diff --git a/internal/component/instances/instances/barrel/scripts/entrypoint.sh b/internal/wisski/instances/barrel/scripts/entrypoint.sh similarity index 100% rename from internal/component/instances/instances/barrel/scripts/entrypoint.sh rename to internal/wisski/instances/barrel/scripts/entrypoint.sh diff --git a/internal/component/instances/instances/barrel/scripts/provision_container.sh b/internal/wisski/instances/barrel/scripts/provision_container.sh similarity index 100% rename from internal/component/instances/instances/barrel/scripts/provision_container.sh rename to internal/wisski/instances/barrel/scripts/provision_container.sh diff --git a/internal/component/instances/instances/barrel/scripts/user_shell.sh b/internal/wisski/instances/barrel/scripts/user_shell.sh similarity index 100% rename from internal/component/instances/instances/barrel/scripts/user_shell.sh rename to internal/wisski/instances/barrel/scripts/user_shell.sh diff --git a/internal/component/instances/instances/barrel/wisskiutils/create_adapter.php b/internal/wisski/instances/barrel/wisskiutils/create_adapter.php similarity index 100% rename from internal/component/instances/instances/barrel/wisskiutils/create_adapter.php rename to internal/wisski/instances/barrel/wisskiutils/create_adapter.php diff --git a/internal/component/instances/instances/barrel/wisskiutils/set_trusted_host.sh b/internal/wisski/instances/barrel/wisskiutils/set_trusted_host.sh similarity index 100% rename from internal/component/instances/instances/barrel/wisskiutils/set_trusted_host.sh rename to internal/wisski/instances/barrel/wisskiutils/set_trusted_host.sh diff --git a/internal/component/instances/instances/barrel/wisskiutils/settings_php_get.sh b/internal/wisski/instances/barrel/wisskiutils/settings_php_get.sh similarity index 100% rename from internal/component/instances/instances/barrel/wisskiutils/settings_php_get.sh rename to internal/wisski/instances/barrel/wisskiutils/settings_php_get.sh diff --git a/internal/component/instances/instances/barrel/wisskiutils/settings_php_set.sh b/internal/wisski/instances/barrel/wisskiutils/settings_php_set.sh similarity index 100% rename from internal/component/instances/instances/barrel/wisskiutils/settings_php_set.sh rename to internal/wisski/instances/barrel/wisskiutils/settings_php_set.sh diff --git a/internal/component/instances/instances/reserve.env b/internal/wisski/instances/reserve.env similarity index 100% rename from internal/component/instances/instances/reserve.env rename to internal/wisski/instances/reserve.env diff --git a/internal/component/instances/instances/reserve/docker-compose.yml b/internal/wisski/instances/reserve/docker-compose.yml similarity index 100% rename from internal/component/instances/instances/reserve/docker-compose.yml rename to internal/wisski/instances/reserve/docker-compose.yml diff --git a/internal/component/instances/instances/reserve/index.html b/internal/wisski/instances/reserve/index.html similarity index 100% rename from internal/component/instances/instances/reserve/index.html rename to internal/wisski/instances/reserve/index.html diff --git a/internal/component/instances/wisski_lock.go b/internal/wisski/lock.go similarity index 73% rename from internal/component/instances/wisski_lock.go rename to internal/wisski/lock.go index 12db452..cb68a8a 100644 --- a/internal/component/instances/wisski_lock.go +++ b/internal/wisski/lock.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( "errors" @@ -10,8 +10,8 @@ var ErrLocked = errors.New("instance is locked") // TryLock attemps to lock this WissKI // If this is not possible, returns ErrLocked -func (wisski WissKI) TryLock() error { - table, err := wisski.instances.SQL.QueryTable(true, models.LockTable) +func (wisski *WissKI) TryLock() error { + table, err := wisski.SQL.QueryTable(true, models.LockTable) if err != nil { return ErrLocked } @@ -25,8 +25,8 @@ func (wisski WissKI) TryLock() error { return nil } -func (wisski WissKI) IsLocked() (locked bool) { - table, err := wisski.instances.SQL.QueryTable(true, models.LockTable) +func (wisski *WissKI) IsLocked() (locked bool) { + table, err := wisski.SQL.QueryTable(true, models.LockTable) if err != nil { return false } @@ -38,7 +38,7 @@ func (wisski WissKI) IsLocked() (locked bool) { // Unlock unlocks this WissKI instance and returns if it succeeded func (wisski WissKI) Unlock() bool { - table, err := wisski.instances.SQL.QueryTable(true, models.LockTable) + table, err := wisski.SQL.QueryTable(true, models.LockTable) if err != nil { return false } diff --git a/internal/wisski/meta.go b/internal/wisski/meta.go new file mode 100644 index 0000000..84e2037 --- /dev/null +++ b/internal/wisski/meta.go @@ -0,0 +1,7 @@ +package wisski + +import "github.com/FAU-CDI/wisski-distillery/internal/component/meta" + +func (wisski *WissKI) storage() *meta.Storage { + return wisski.Meta.Storage(wisski.Slug) +} diff --git a/internal/component/instances/wisski_pathbuilders.go b/internal/wisski/pathbuilders.go similarity index 98% rename from internal/component/instances/wisski_pathbuilders.go rename to internal/wisski/pathbuilders.go index 6ccc50a..2c00e3c 100644 --- a/internal/component/instances/wisski_pathbuilders.go +++ b/internal/wisski/pathbuilders.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( _ "embed" diff --git a/internal/component/instances/wisski_php.go b/internal/wisski/php.go similarity index 95% rename from internal/component/instances/wisski_php.go rename to internal/wisski/php.go index f8761e4..c4c086f 100644 --- a/internal/component/instances/wisski_php.go +++ b/internal/wisski/php.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( _ "embed" diff --git a/internal/component/instances/php/export_pathbuilder.php b/internal/wisski/php/export_pathbuilder.php similarity index 100% rename from internal/component/instances/php/export_pathbuilder.php rename to internal/wisski/php/export_pathbuilder.php diff --git a/internal/component/instances/php/list_uri_prefixes.php b/internal/wisski/php/list_uri_prefixes.php similarity index 100% rename from internal/component/instances/php/list_uri_prefixes.php rename to internal/wisski/php/list_uri_prefixes.php diff --git a/internal/component/instances/php/server.php b/internal/wisski/php/server.php similarity index 100% rename from internal/component/instances/php/server.php rename to internal/wisski/php/server.php diff --git a/internal/component/instances/php/settings.php b/internal/wisski/php/settings.php similarity index 100% rename from internal/component/instances/php/settings.php rename to internal/wisski/php/settings.php diff --git a/internal/component/instances/wisski_php_server.go b/internal/wisski/php_server.go similarity index 99% rename from internal/component/instances/wisski_php_server.go rename to internal/wisski/php_server.go index 6bc66e6..d09eea7 100644 --- a/internal/component/instances/wisski_php_server.go +++ b/internal/wisski/php_server.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( "context" diff --git a/internal/component/instances/wisski_prefix.go b/internal/wisski/prefix.go similarity index 80% rename from internal/component/instances/wisski_prefix.go rename to internal/wisski/prefix.go index 8fe224f..0e00330 100644 --- a/internal/component/instances/wisski_prefix.go +++ b/internal/wisski/prefix.go @@ -1,10 +1,11 @@ -package instances +package wisski import ( "bufio" "path/filepath" "strings" + "github.com/FAU-CDI/wisski-distillery/internal/component/meta" "github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/tkw1536/goprogram/lib/collection" @@ -14,7 +15,7 @@ import ( // 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.instances.Environment, filepath.Join(wisski.FilesystemBase, "prefixes.skip")) + return fsx.IsFile(wisski.Core.Environment, filepath.Join(wisski.FilesystemBase, "prefixes.skip")) } //go:embed php/list_uri_prefixes.php @@ -51,7 +52,7 @@ func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err erro }) // load the list of blocked prefixes - blocks, err := wisski.instances.blockedPrefixes() + blocks, err := wisski.blockedPrefixes() if err != nil { return nil, err } @@ -60,9 +61,9 @@ func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err erro return collection.Filter(prefixes, func(uri string) bool { return !hasAnyPrefix(uri, blocks) }), nil } -func (instances *Instances) blockedPrefixes() ([]string, error) { +func (wisski *WissKI) blockedPrefixes() ([]string, error) { // open the resolver block file - file, err := instances.Environment.Open(instances.Config.SelfResolverBlockFile) + file, err := wisski.Core.Environment.Open(wisski.Core.Config.SelfResolverBlockFile) if err != nil { return nil, err } @@ -99,11 +100,11 @@ func hasAnyPrefix(candidate string, prefixes []string) bool { func (wisski *WissKI) filePrefixes() (prefixes []string, err error) { path := filepath.Join(wisski.FilesystemBase, "prefixes") - if !fsx.IsFile(wisski.instances.Environment, path) { + if !fsx.IsFile(wisski.Core.Environment, path) { return nil, nil } - file, err := wisski.instances.Environment.Open(path) + file, err := wisski.Core.Environment.Open(path) if err != nil { return nil, err } @@ -126,17 +127,11 @@ func (wisski *WissKI) filePrefixes() (prefixes []string, err error) { // CACHING -var PrefixConfigKey MetaKey = "prefix" +var prefix = meta.StorageFor[string]("prefix") // Prefixes returns the cached prefixes from the given instance func (wisski *WissKI) PrefixesCached() (results []string, err error) { - err = wisski.Metadata().GetAll(PrefixConfigKey, func(index, total int) any { - if results == nil { - results = make([]string, total) - } - return &results[index] - }) - return + return prefix(wisski.storage()).GetAll() } // UpdatePrefixes updates the cached prefixes of this instance @@ -145,6 +140,5 @@ func (wisski *WissKI) UpdatePrefixes() error { if err != nil { return err } - - return wisski.Metadata().SetAll(PrefixConfigKey, collection.AsAny(prefixes)...) + return prefix(wisski.storage()).SetAll(prefixes...) } diff --git a/internal/component/instances/wisski_props.go b/internal/wisski/props.go similarity index 75% rename from internal/component/instances/wisski_props.go rename to internal/wisski/props.go index d488f08..99b5b85 100644 --- a/internal/component/instances/wisski_props.go +++ b/internal/wisski/props.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( "fmt" @@ -7,7 +7,7 @@ import ( // Domain returns the full domain name of this WissKI func (wisski WissKI) Domain() string { - return fmt.Sprintf("%s.%s", wisski.Slug, wisski.instances.Config.DefaultDomain) + return fmt.Sprintf("%s.%s", wisski.Slug, wisski.Core.Config.DefaultDomain) } // URL returns the public URL of this instance @@ -19,7 +19,7 @@ func (wisski WissKI) URL() *url.URL { } // use http or https scheme depending on if the distillery has it enabled - if wisski.instances.Config.HTTPSEnabled() { + if wisski.Core.Config.HTTPSEnabled() { url.Scheme = "https" } else { url.Scheme = "http" diff --git a/internal/wisski/provision.go b/internal/wisski/provision.go new file mode 100644 index 0000000..e8975a6 --- /dev/null +++ b/internal/wisski/provision.go @@ -0,0 +1,55 @@ +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 new file mode 100644 index 0000000..d442414 --- /dev/null +++ b/internal/wisski/snapshots.go @@ -0,0 +1,8 @@ +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.SnapshotsLog.For(wisski.Slug) +} diff --git a/internal/component/instances/wisski_stack.go b/internal/wisski/stack.go similarity index 69% rename from internal/component/instances/wisski_stack.go rename to internal/wisski/stack.go index ea80bb5..1d7bdd9 100644 --- a/internal/component/instances/wisski_stack.go +++ b/internal/wisski/stack.go @@ -1,4 +1,4 @@ -package instances +package wisski import ( "embed" @@ -6,6 +6,7 @@ import ( "time" "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/meta" "github.com/tkw1536/goprogram/stream" ) @@ -17,7 +18,7 @@ func (wisski *WissKI) Barrel() component.StackWithResources { return component.StackWithResources{ Stack: component.Stack{ Dir: wisski.FilesystemBase, - Env: wisski.instances.Environment, + Env: wisski.Core.Environment, }, Resources: barrelResources, @@ -25,15 +26,15 @@ func (wisski *WissKI) Barrel() component.StackWithResources { EnvPath: filepath.Join("instances", "barrel.env"), EnvContext: map[string]string{ - "DOCKER_NETWORK_NAME": wisski.instances.Config.DockerNetworkName, + "DOCKER_NETWORK_NAME": wisski.Core.Config.DockerNetworkName, "SLUG": wisski.Slug, "VIRTUAL_HOST": wisski.Domain(), - "HTTPS_ENABLED": wisski.instances.Config.HTTPSEnabledEnv(), + "HTTPS_ENABLED": wisski.Core.Config.HTTPSEnabledEnv(), "DATA_PATH": filepath.Join(wisski.FilesystemBase, "data"), - "RUNTIME_DIR": wisski.instances.Config.RuntimeDir(), - "GLOBAL_AUTHORIZED_KEYS_FILE": wisski.instances.Config.GlobalAuthorizedKeysFile, + "RUNTIME_DIR": wisski.Core.Config.RuntimeDir(), + "GLOBAL_AUTHORIZED_KEYS_FILE": wisski.Core.Config.GlobalAuthorizedKeysFile, }, MakeDirs: []string{"data", ".composer"}, @@ -44,14 +45,12 @@ func (wisski *WissKI) Barrel() component.StackWithResources { } } -const KeyLastRebuild MetaKey = "lastRebuild" +// TODO: Move this to time.Time +var lastRebuild = meta.StorageFor[int64]("lastRebuild") func (wisski *WissKI) LastRebuild() (t time.Time, err error) { - var epoch int64 - - // read the epoch! - err = wisski.Metadata().Get(KeyLastRebuild, &epoch) - if err == ErrMetadatumNotSet { + epoch, err := lastRebuild(wisski.storage()).Get() + if err == meta.ErrMetadatumNotSet { return t, nil } if err != nil { @@ -63,7 +62,7 @@ func (wisski *WissKI) LastRebuild() (t time.Time, err error) { } func (wisski *WissKI) setLastRebuild() error { - return wisski.Metadata().Set(KeyLastRebuild, time.Now().Unix()) + return lastRebuild(wisski.storage()).Set(time.Now().Unix()) } // Build builds or rebuilds the barel connected to this instance. @@ -105,7 +104,7 @@ func (wisski *WissKI) Reserve() component.StackWithResources { return component.StackWithResources{ Stack: component.Stack{ Dir: wisski.FilesystemBase, - Env: wisski.instances.Environment, + Env: wisski.Core.Environment, }, Resources: reserveResources, @@ -113,11 +112,16 @@ func (wisski *WissKI) Reserve() component.StackWithResources { EnvPath: filepath.Join("instances", "reserve.env"), EnvContext: map[string]string{ - "DOCKER_NETWORK_NAME": wisski.instances.Config.DockerNetworkName, + "DOCKER_NETWORK_NAME": wisski.Core.Config.DockerNetworkName, "SLUG": wisski.Slug, "VIRTUAL_HOST": wisski.Domain(), - "HTTPS_ENABLED": wisski.instances.Config.HTTPSEnabledEnv(), + "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/component/instances/wisski_update.go b/internal/wisski/update.go similarity index 77% rename from internal/component/instances/wisski_update.go rename to internal/wisski/update.go index cc8c15f..abc8a9c 100644 --- a/internal/component/instances/wisski_update.go +++ b/internal/wisski/update.go @@ -1,8 +1,9 @@ -package instances +package wisski import ( "time" + "github.com/FAU-CDI/wisski-distillery/internal/component/meta" "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/stream" @@ -26,14 +27,11 @@ func (wisski *WissKI) BlindUpdate(io stream.IOStream) error { return wisski.setLastUpdate() } -const KeyLastUpdate MetaKey = "lastUpdate" +var lastUpdate = meta.StorageFor[int64]("lastUpdate") func (wisski *WissKI) LastUpdate() (t time.Time, err error) { - var epoch int64 - - // read the epoch! - err = wisski.Metadata().Get(KeyLastUpdate, &epoch) - if err == ErrMetadatumNotSet { + epoch, err := lastUpdate(wisski.storage()).Get() + if err == meta.ErrMetadatumNotSet { return t, nil } if err != nil { @@ -45,5 +43,5 @@ func (wisski *WissKI) LastUpdate() (t time.Time, err error) { } func (wisski *WissKI) setLastUpdate() error { - return wisski.Metadata().Set(KeyLastUpdate, time.Now().Unix()) + return lastUpdate(wisski.storage()).Set(time.Now().Unix()) } diff --git a/internal/wisski/wisski.go b/internal/wisski/wisski.go new file mode 100644 index 0000000..5618e8b --- /dev/null +++ b/internal/wisski/wisski.go @@ -0,0 +1,60 @@ +// Package wisski provides WissKI +package wisski + +import ( + "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/meta" + "github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog" + "github.com/FAU-CDI/wisski-distillery/internal/component/sql" + "github.com/FAU-CDI/wisski-distillery/internal/component/triplestore" + "github.com/FAU-CDI/wisski-distillery/internal/models" +) + +// WissKI represents a single WissKI Instance +type WissKI struct { + models.Instance // whatever is stored inside the underlying instance + + // Drupal credentials - not stored in the database + DrupalUsername string + DrupalPassword string + + // references to components! + Core component.Core + Meta *meta.Meta + TS *triplestore.Triplestore + SQL *sql.SQL + + SnapshotsLog *snapshotslog.SnapshotsLog +} + +// 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 + } + + // 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 +} + +// 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 +} diff --git a/pkg/lazy/pool.go b/pkg/lazy/pool.go index 1734cb7..07943c7 100644 --- a/pkg/lazy/pool.go +++ b/pkg/lazy/pool.go @@ -56,7 +56,7 @@ type PoolContext[Component any] struct { // function to return all components - metaCache sync.Map + metaCache sync.Map // Map[string]meta[Component] cache map[string]Component // cached components queue []delayedInit[Component] // init queue }