Move wisski instance code to separate package

This commit is contained in:
Tom Wiesing 2022-10-17 14:20:15 +02:00
parent 7c3c84e116
commit 063f3f9b7d
No known key found for this signature in database
67 changed files with 533 additions and 409 deletions

View file

@ -41,12 +41,12 @@ func (bk backup) Run(context wisski_distillery.Context) error {
// prune old backups
if !bk.NoPrune {
defer logging.LogOperation(func() error {
return dis.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,

View file

@ -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)
}))
}

View file

@ -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)
}))
}

View file

@ -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() {

View file

@ -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)
}))
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
}))
}

View file

@ -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

View file

@ -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
})

View file

@ -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" }

View file

@ -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) {

View file

@ -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)
},
}

View file

@ -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
}

View file

@ -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"
@ -19,6 +22,8 @@ type Instances struct {
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

View file

@ -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
}

View file

@ -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
}

View file

@ -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...)...)
}

View file

@ -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]
}

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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

View file

@ -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"
@ -19,6 +20,7 @@ type Manager struct {
SQL *sql.SQL
Instances *instances.Instances
SnapshotsLog *snapshotslog.SnapshotsLog
Snapshotable []component.Snapshotable
Backupable []component.Backupable

View file

@ -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
}

View file

@ -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()

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -1,4 +1,4 @@
package instances
package wisski
import (
"time"

View file

@ -1,4 +1,4 @@
package instances
package wisski
import (
"time"

View file

@ -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
}

7
internal/wisski/meta.go Normal file
View file

@ -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)
}

View file

@ -1,4 +1,4 @@
package instances
package wisski
import (
_ "embed"

View file

@ -1,4 +1,4 @@
package instances
package wisski
import (
_ "embed"

View file

@ -1,4 +1,4 @@
package instances
package wisski
import (
"context"

View file

@ -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...)
}

View file

@ -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"

View file

@ -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
}

View file

@ -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)
}

View file

@ -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...)...)
}

View file

@ -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())
}

60
internal/wisski/wisski.go Normal file
View file

@ -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
}

View file

@ -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
}