Move WissKI Parts to new ingredients system

This commit is contained in:
Tom Wiesing 2022-10-18 10:44:39 +02:00
parent b5b1ce2340
commit 42b8cbd865
No known key found for this signature in database
83 changed files with 1016 additions and 646 deletions

View file

@ -52,7 +52,7 @@ func (bu blindUpdate) Run(context wisski_distillery.Context) error {
// and do the actual blind_update!
return status.StreamGroup(context.IOStream, bu.Parallel, func(instance *wisski.WissKI, str stream.IOStream) error {
return instance.BlindUpdate(str)
return instance.Drush().Update(str)
}, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("blind_update %q", item.Slug)
}))

View file

@ -40,7 +40,7 @@ func (cr cron) Run(context wisski_distillery.Context) error {
// and do the actual blind_update!
return status.StreamGroup(context.IOStream, cr.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error {
return instance.Cron(io)
return instance.Drush().Cron(io)
}, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("cron %q", item.Slug)
}))

View file

@ -47,7 +47,7 @@ func (ds setting) Run(context wisski_distillery.Context) error {
if ds.Positionals.Value == "" {
// get the setting
value, err := instance.GetSettingsPHP(nil, ds.Positionals.Setting)
value, err := instance.Settings().Get(nil, ds.Positionals.Setting)
if err != nil {
return errSettingGet.Wrap(err)
}
@ -69,7 +69,7 @@ func (ds setting) Run(context wisski_distillery.Context) error {
}
// set the serialized value!
if err := instance.SetSettingsPHP(nil, ds.Positionals.Setting, data); err != nil {
if err := instance.Settings().Set(nil, ds.Positionals.Setting, data); err != nil {
return errSettingSet.Wrap(err)
}

View file

@ -34,7 +34,7 @@ func (i info) Run(context wisski_distillery.Context) error {
return err
}
info, err := instance.Info(false)
info, err := instance.Info().Fetch(false)
if err != nil {
return err
}

View file

@ -3,6 +3,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/cli"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/tkw1536/goprogram/exit"
)
@ -51,15 +52,15 @@ func (l instanceLock) Run(context wisski_distillery.Context) error {
}
if l.Unlock {
if !instance.Unlock() {
if !instance.Locker().TryUnlock() {
return errNotUnlock
}
context.Println("unlocked")
return nil
}
if err := instance.TryLock(); err != nil {
return err
if !instance.Locker().TryLock() {
return locker.Locked
}
context.Println("locked")

View file

@ -46,7 +46,7 @@ func (pb pathbuilders) Run(context wisski_distillery.Context) error {
// get all of the pathbuilders
if pb.Positionals.Name == "" {
names, err := instance.Pathbuilders(nil)
names, err := instance.Pathbuilder().All(nil)
if err != nil {
return errPathbuilders.WithMessageF(err)
}
@ -57,7 +57,7 @@ func (pb pathbuilders) Run(context wisski_distillery.Context) error {
}
// get all the pathbuilders
xml, err := instance.Pathbuilder(nil, pb.Positionals.Name)
xml, err := instance.Pathbuilder().Get(nil, pb.Positionals.Name)
if xml == "" {
return errNoPathbuilder.WithMessageF(pb.Positionals.Name)
}

View file

@ -36,7 +36,7 @@ func (p prefixes) Run(context wisski_distillery.Context) error {
return err
}
prefixes, err := instance.Prefixes(nil)
prefixes, err := instance.Prefixes().All(nil)
if err != nil {
return errPrefixesGeneric.Wrap(err)
}

View file

@ -63,7 +63,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// Store in the instances table!
if err := logging.LogOperation(func() error {
if err := instance.Save(); err != nil {
if err := instance.Bookkeeping().Save(); err != nil {
return errProvisionGeneric.WithMessageF(slug, err)
}
@ -90,7 +90,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// run the provision script
if err := logging.LogOperation(func() error {
if err := instance.Provision(context.IOStream); err != nil {
if err := instance.Provisioner().Provision(context.IOStream); err != nil {
return errProvisionGeneric.WithMessageF(slug, err)
}
@ -101,7 +101,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// start the container!
logging.LogMessage(context.IOStream, "Starting Container")
if err := instance.Barrel().Up(context.IOStream); err != nil {
if err := instance.Barrel().Stack().Up(context.IOStream); err != nil {
return err
}

View file

@ -65,7 +65,7 @@ func (p purge) Run(context wisski_distillery.Context) error {
// remove docker stack
logging.LogMessage(context.IOStream, "Stopping and removing docker container")
if err := instance.Barrel().Down(context.IOStream); err != nil {
if err := instance.Barrel().Stack().Down(context.IOStream); err != nil {
context.EPrintln(err)
}
@ -93,13 +93,13 @@ func (p purge) Run(context wisski_distillery.Context) error {
// remove from bookkeeping
logging.LogMessage(context.IOStream, "Removing instance from bookkeeping")
if err := instance.Delete(); err != nil {
if err := instance.Bookkeeping().Delete(); err != nil {
context.EPrintln(err)
}
// remove the filesystem
logging.LogMessage(context.IOStream, "Remove lock data", instance.FilesystemBase)
if !instance.Unlock() {
if instance.Locker().TryUnlock() {
context.EPrintln("instance was not locked")
}

View file

@ -47,7 +47,7 @@ func (rb rebuild) Run(context wisski_distillery.Context) error {
// and do the actual rebuild
return status.StreamGroup(context.IOStream, rb.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error {
return instance.Build(io, true)
return instance.Barrel().Build(io, true)
}, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string {
return fmt.Sprintf("rebuild %q", item.Slug)
}))

View file

@ -63,7 +63,7 @@ func (r reserve) Run(context wisski_distillery.Context) error {
}
// setup docker stack
s := instance.Reserve()
s := instance.Reserve().Stack()
{
if err := logging.LogOperation(func() error {
return s.Install(context.IOStream, component.InstallationContext{})

View file

@ -43,7 +43,7 @@ func (sh shell) Run(context wisski_distillery.Context) error {
return err
}
code, err := instance.Shell(context.IOStream, sh.Positionals.Args...)
code, err := instance.Barrel().Shell(context.IOStream, sh.Positionals.Args...)
if err != nil {
return errShell.WithMessageF(err)
}

View file

@ -44,7 +44,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
return status.StreamGroup(context.IOStream, upc.Parallel, func(instance *wisski.WissKI, io stream.IOStream) error {
io.Println("reading prefixes")
err := instance.UpdatePrefixes()
err := instance.Prefixes().UpdatePrefixes()
if err != nil {
return errPrefixUpdateFailed.Wrap(err)
}

View file

@ -34,10 +34,6 @@ func auto[C component.Component](context ctx) component.Component {
// register returns all components of the distillery
func (dis *Distillery) register(context ctx) []component.Component {
dis.poolInit.Do(func() {
dis.pool.Init = component.Init
})
return collection.MapSlice(
dis.allComponents(),
func(f initFunc) component.Component {

View file

@ -19,7 +19,7 @@ func (Pathbuilders) SnapshotName() string { return "pathbuilders" }
func (pbs *Pathbuilders) Snapshot(wisski models.Instance, context component.StagingContext) error {
return context.AddDirectory(".", func() error {
builders, err := pbs.Instances.Instance(wisski).AllPathbuilders(nil)
builders, err := pbs.Instances.Instance(wisski).Pathbuilder().GetAll(nil)
if err != nil {
return err
}

View file

@ -9,6 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/lib/collection"
"github.com/tkw1536/goprogram/status"
@ -46,7 +47,8 @@ type Snapshot struct {
func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) {
logging.LogMessage(io, "Locking instance")
if err := instance.TryLock(); err != nil {
if !instance.Locker().TryLock() {
err := locker.Locked
io.EPrintln(err)
logging.LogMessage(io, "Aborting snapshot creation")
@ -56,7 +58,7 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre
}
defer func() {
logging.LogMessage(io, "Unlocking instance")
instance.Unlock()
instance.Locker().Unlock()
}()
// setup the snapshot
@ -85,7 +87,7 @@ func (snapshots *Exporter) NewSnapshot(instance *wisski.WissKI, io stream.IOStre
func (snapshot *Snapshot) makeParts(ios stream.IOStream, snapshots *Exporter, instance *wisski.WissKI, needsRunning bool) map[string]error {
if !needsRunning && !snapshot.Description.Keepalive {
stack := instance.Barrel()
stack := instance.Barrel().Stack()
logging.LogMessage(ios, "Stopping instance")
snapshot.ErrStop = stack.Down(ios)

View file

@ -8,7 +8,7 @@ import (
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/static"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"github.com/FAU-CDI/wisski-distillery/pkg/timex"
"github.com/tkw1536/goprogram/stream"
"golang.org/x/sync/errgroup"
@ -65,7 +65,7 @@ func (home *Home) homeRender() ([]byte, error) {
if err != nil {
return nil, err
}
context.Instances = make([]wisski.WissKIInfo, len(wissKIs))
context.Instances = make([]info.WissKIInfo, len(wissKIs))
// determine their infos
var eg errgroup.Group
@ -73,7 +73,7 @@ func (home *Home) homeRender() ([]byte, error) {
i := i
wissKI := instance
eg.Go(func() (err error) {
context.Instances[i], err = wissKI.Info(true)
context.Instances[i], err = wissKI.Info().Fetch(true)
return
})
}
@ -86,7 +86,7 @@ func (home *Home) homeRender() ([]byte, error) {
}
type HomeContext struct {
Instances []wisski.WissKIInfo
Instances []info.WissKIInfo
Time time.Time

View file

@ -9,7 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/static"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"golang.org/x/sync/errgroup"
)
@ -22,7 +22,7 @@ type indexPageContext struct {
Config *config.Config
Instances []wisski.WissKIInfo
Instances []info.WissKIInfo
TotalCount int
RunningCount int
@ -31,18 +31,18 @@ type indexPageContext struct {
Backups []models.Export
}
func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error) {
func (nfo *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error) {
var group errgroup.Group
group.Go(func() error {
// list all the instances
all, err := info.Instances.All()
all, err := nfo.Instances.All()
if err != nil {
return err
}
// get all of their info!
idx.Instances = make([]wisski.WissKIInfo, len(all))
idx.Instances = make([]info.WissKIInfo, len(all))
for i, instance := range all {
{
i := i
@ -50,7 +50,7 @@ func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error
// store the info for this group!
group.Go(func() (err error) {
idx.Instances[i], err = instance.Info(true)
idx.Instances[i], err = instance.Info().Fetch(true)
return err
})
}
@ -61,12 +61,12 @@ func (info *Info) indexPageAPI(r *http.Request) (idx indexPageContext, err error
// get the log entries
group.Go(func() (err error) {
idx.Backups, err = info.SnapshotsLog.For("")
idx.Backups, err = nfo.SnapshotsLog.For("")
return
})
// get the static properties
idx.Config = info.Config
idx.Config = nfo.Config
idx.Time = time.Now().UTC()
group.Wait()

View file

@ -9,7 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/static"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
)
@ -21,7 +21,7 @@ type instancePageContext struct {
Time time.Time
Instance models.Instance
Info wisski.WissKIInfo
Info info.WissKIInfo
}
func (info *Info) instancePageAPI(r *http.Request) (is instancePageContext, err error) {
@ -40,7 +40,7 @@ func (info *Info) instancePageAPI(r *http.Request) (is instancePageContext, err
is.Instance = instance.Instance
// get some more info about the wisski
is.Info, err = instance.Info(false)
is.Info, err = instance.Info().Fetch(false)
if err != nil {
return is, err
}

View file

@ -23,13 +23,13 @@ var socketInstanceActions = map[string]instanceActionFunc{
)
},
"rebuild": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Build(str, true)
return instance.Barrel().Build(str, true)
},
"update": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.BlindUpdate(str)
return instance.Drush().Update(str)
},
"cron": func(_ *Info, instance *wisski.WissKI, str stream.IOStream) error {
return instance.Cron(str)
return instance.Drush().Cron(str)
},
}

View file

@ -25,37 +25,37 @@ func (instances *Instances) Create(slug string) (wissKI *wisski.WissKI, err erro
wissKI = new(wisski.WissKI)
instances.use(wissKI)
wissKI.Instance.Slug = slug
wissKI.Instance.FilesystemBase = filepath.Join(instances.Path(), wissKI.Domain())
wissKI.Liquid.Instance.Slug = slug
wissKI.Liquid.Instance.FilesystemBase = filepath.Join(instances.Path(), wissKI.Domain())
wissKI.Instance.OwnerEmail = ""
wissKI.Instance.AutoBlindUpdateEnabled = true
wissKI.Liquid.Instance.OwnerEmail = ""
wissKI.Liquid.Instance.AutoBlindUpdateEnabled = true
// sql
wissKI.Instance.SqlDatabase = instances.Config.MysqlDatabasePrefix + slug
wissKI.Instance.SqlUsername = instances.Config.MysqlUserPrefix + slug
wissKI.Liquid.Instance.SqlDatabase = instances.Config.MysqlDatabasePrefix + slug
wissKI.Liquid.Instance.SqlUsername = instances.Config.MysqlUserPrefix + slug
wissKI.Instance.SqlPassword, err = instances.Config.NewPassword()
wissKI.Liquid.Instance.SqlPassword, err = instances.Config.NewPassword()
if err != nil {
return nil, err
}
// triplestore
wissKI.Instance.GraphDBRepository = instances.Config.GraphDBRepoPrefix + slug
wissKI.Instance.GraphDBUsername = instances.Config.GraphDBUserPrefix + slug
wissKI.Liquid.Instance.GraphDBRepository = instances.Config.GraphDBRepoPrefix + slug
wissKI.Liquid.Instance.GraphDBUsername = instances.Config.GraphDBUserPrefix + slug
wissKI.Instance.GraphDBPassword, err = instances.Config.NewPassword()
wissKI.Liquid.Instance.GraphDBPassword, err = instances.Config.NewPassword()
if err != nil {
return nil, err
}
// drupal
wissKI.DrupalUsername = "admin" // TODO: Change this!
wissKI.Liquid.DrupalUsername = "admin" // TODO: Change this!
wissKI.DrupalPassword, err = instances.Config.NewPassword()
wissKI.Liquid.DrupalPassword, err = instances.Config.NewPassword()
if err != nil {
return nil, err
}

View file

@ -6,10 +6,8 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/tkw1536/goprogram/exit"
@ -21,10 +19,8 @@ import (
type Instances struct {
component.Base
TS *triplestore.Triplestore
Malt *malt.Malt
SQL *sql.SQL
Meta *meta.Meta
ExporterLog *logger.Logger
}
func (instances *Instances) Path() string {
@ -41,11 +37,7 @@ var errSQL = exit.Error{
// use uses the non-nil wisski instance with this instances
func (instances *Instances) use(wisski *wisski.WissKI) {
wisski.Core = instances.Still
wisski.SQL = instances.SQL
wisski.TS = instances.TS
wisski.Meta = instances.Meta
wisski.ExporterLog = instances.ExporterLog
wisski.Liquid.Malt = instances.Malt
}
// WissKI returns the WissKI with the provided slug, if it exists.
@ -65,7 +57,7 @@ func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err erro
wissKI = new(wisski.WissKI)
// find the instance by slug
query := table.Where(&models.Instance{Slug: slug}).Find(&wissKI.Instance)
query := table.Where(&models.Instance{Slug: slug}).Find(&wissKI.Liquid.Instance)
switch {
case query.Error != nil:
return nil, errSQL.WithMessageF(query.Error)
@ -166,7 +158,7 @@ func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB
results = make([]*wisski.WissKI, len(bks))
for i, bk := range bks {
results[i] = new(wisski.WissKI)
results[i].Instance = bk
results[i].Liquid.Instance = bk
instances.use(results[i])
}

View file

@ -0,0 +1,19 @@
package malt
import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore"
)
// Malt is a component passed to every WissKI ingredient
type Malt struct {
component.Base
TS *triplestore.Triplestore
SQL *sql.SQL
Meta *meta.Meta
ExporterLog *logger.Logger
}

View file

@ -6,7 +6,6 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/tkw1536/goprogram/lib/collection"
"gorm.io/gorm"
)
@ -177,42 +176,3 @@ func (s Storage) Purge() error {
}
return nil
}
// StorageFor returns a storage for the given key.
func StorageFor[Value any](key Key) func(storage *Storage) SpecifcStorage[Value] {
return func(storage *Storage) SpecifcStorage[Value] {
return SpecifcStorage[Value]{storage: storage, key: key}
}
}
type SpecifcStorage[Value any] struct {
storage *Storage
key Key
}
func (sf SpecifcStorage[Value]) Get() (value Value, err error) {
err = sf.storage.Get(sf.key, &value)
return
}
func (sf SpecifcStorage[Value]) GetAll() (values []Value, err error) {
err = sf.storage.GetAll(sf.key, func(index, total int) any {
if values == nil {
values = make([]Value, total)
}
return &values[index]
})
return values, err
}
func (sf SpecifcStorage[Value]) Set(value Value) error {
return sf.storage.Set(sf.key, value)
}
func (sf SpecifcStorage[Value]) SetAll(values ...Value) error {
return sf.storage.SetAll(sf.key, collection.AsAny(values)...)
}
func (sf SpecifcStorage[Value]) Delete() error {
return sf.storage.Delete(sf.key)
}

View file

@ -30,14 +30,14 @@ func (resolver *Resolver) AllPrefixes() (map[string]string, error) {
gPrefixes := make(map[string]string)
var lastErr error
for _, instance := range instances {
if instance.NoPrefix() {
if instance.Prefixes().NoPrefix() {
continue
}
url := instance.URL().String()
// failed to fetch prefixes for this particular instance
// => skip it!
prefixes, err := instance.PrefixesCached()
prefixes, err := instance.Prefixes().PrefixesCached()
if err != nil {
lastErr = err
continue

View file

@ -13,6 +13,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/home"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/info"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/resolver"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
@ -114,8 +115,10 @@ func (dis *Distillery) allComponents() []initFunc {
sql.PollInterval = time.Second
}),
// instainces
auto[*instances.Instances],
auto[*meta.Meta],
auto[*malt.Malt],
// Snapshots
auto[*exporter.Exporter],

View file

@ -0,0 +1,15 @@
package barrel
import (
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
)
// Barrel provides access to the underlying Barrel
type Barrel struct {
ingredient.Base
Locker *locker.Locker
MStore *mstore.MStore
}

View file

@ -0,0 +1,63 @@
package barrel
import (
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
"github.com/tkw1536/goprogram/stream"
)
// Build builds or rebuilds the barel connected to this instance.
//
// It also logs the current time into the metadata belonging to this instance.
func (barrel *Barrel) Build(stream stream.IOStream, start bool) error {
if !barrel.Locker.TryLock() {
err := locker.Locked
return err
}
defer barrel.Locker.Unlock()
stack := barrel.Stack()
var context component.InstallationContext
{
err := stack.Install(stream, context)
if err != nil {
return err
}
}
{
err := stack.Update(stream, start)
if err != nil {
return err
}
}
// store the current last rebuild
return barrel.setLastRebuild()
}
// TODO: Move this to time.Time
var lastRebuild = mstore.For[int64]("lastRebuild")
func (barrel Barrel) LastRebuild() (t time.Time, err error) {
epoch, err := lastRebuild.Get(barrel.MStore)
if err == meta.ErrMetadatumNotSet {
return t, nil
}
if err != nil {
return t, err
}
// and turn it into time!
return time.Unix(epoch, 0), nil
}
func (barrel *Barrel) setLastRebuild() error {
return lastRebuild.Set(barrel.MStore, time.Now().Unix())
}

View file

@ -1,8 +1,9 @@
package wisski
package drush
import (
"time"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/stream"
)
@ -12,23 +13,23 @@ var errCronFailed = exit.Error{
ExitCode: exit.ExitGeneric,
}
func (wisski *WissKI) Cron(io stream.IOStream) error {
code, err := wisski.Shell(io, "/runtime/cron.sh")
func (drush *Drush) Cron(io stream.IOStream) error {
code, err := drush.Barrel.Shell(io, "/runtime/cron.sh")
if err != nil {
io.EPrintln(err)
}
if code != 0 {
// keep going, because we want to run as many crons as possible
err = errBlindUpdateFailed.WithMessageF(wisski.Slug, code)
err = errCronFailed.WithMessageF(drush.Slug, code)
io.EPrintln(err)
}
return nil
}
func (wisski *WissKI) LastCron(server *PHPServer) (t time.Time, err error) {
func (drush *Drush) LastCron(server *php.Server) (t time.Time, err error) {
var timestamp int64
err = wisski.EvalPHPCode(server, &timestamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `)
err = drush.PHP.EvalCode(server, &timestamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `)
if err != nil {
return
}

View file

@ -0,0 +1,17 @@
package drush
import (
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php"
)
// Drush implements commands related to drush
type Drush struct {
ingredient.Base
Barrel *barrel.Barrel
MStore *mstore.MStore
PHP *php.PHP
}

View file

@ -0,0 +1,48 @@
package drush
import (
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/stream"
)
var errBlindUpdateFailed = exit.Error{
Message: "Failed to run blind update script for instance %q: exited with code %s",
ExitCode: exit.ExitGeneric,
}
// Update performs a blind drush update
func (drush *Drush) Update(io stream.IOStream) error {
code, err := drush.Barrel.Shell(io, "/runtime/blind_update.sh")
if err != nil {
return errBlindUpdateFailed.WithMessageF(drush.Slug, environment.ExecCommandError)
}
if code != 0 {
return errBlindUpdateFailed.WithMessageF(drush.Slug, code)
}
return drush.setLastUpdate()
}
const lastUpdate = mstore.For[int64]("lastUpdate")
func (drush *Drush) LastUpdate() (t time.Time, err error) {
epoch, err := lastUpdate.Get(drush.MStore)
if err == meta.ErrMetadatumNotSet {
return t, nil
}
if err != nil {
return t, err
}
// and turn it into time!
return time.Unix(epoch, 0), nil
}
func (drush *Drush) setLastUpdate() error {
return lastUpdate.Set(drush.MStore, time.Now().Unix())
}

View file

@ -0,0 +1,65 @@
package provisioner
import (
"errors"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
"github.com/alessio/shellescape"
"github.com/tkw1536/goprogram/stream"
)
// Provisioner provides provisioning for a barrel
// NOTE(twiesing): This should be refactored to not use the provision script.
// Instead, this should code directly defined in go.
type Provisioner struct {
ingredient.Base
Barrel *barrel.Barrel
}
// Provision provisions an instance, assuming that the required databases already exist.
func (provision *Provisioner) Provision(io stream.IOStream) error {
// build the container
if err := provision.Barrel.Build(io, false); err != nil {
return err
}
provisionParams := []string{
provision.Domain(),
provision.SqlDatabase,
provision.SqlUsername,
provision.SqlPassword,
provision.GraphDBRepository,
provision.GraphDBUsername,
provision.GraphDBPassword,
provision.DrupalUsername,
provision.DrupalPassword,
"", // TODO: DrupalVersion
"", // TODO: WissKIVersion
}
// escape the parameter
for i, param := range provisionParams {
provisionParams[i] = shellescape.Quote(param)
}
// figure out the provision script
// TODO: Move the provision script into the control plane!
provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ")
code, err := provision.Barrel.Stack().Run(io, true, "barrel", "/bin/bash", "-c", provisionScript)
if err != nil {
return err
}
if code != 0 {
return errors.New("unable to run provision script")
}
return nil
}

View file

@ -0,0 +1,12 @@
package barrel
import "github.com/tkw1536/goprogram/stream"
// Running checks if this WissKI is currently running.
func (barrel *Barrel) Running() (bool, error) {
ps, err := barrel.Stack().Ps(stream.FromNil())
if err != nil {
return false, err
}
return len(ps) > 0, nil
}

View file

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

View file

@ -0,0 +1,43 @@
package barrel
import (
"embed"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
)
//go:embed all:barrel barrel.env
var barrelResources embed.FS
// Barrel returns a stack representing the running WissKI Instance
func (barrel *Barrel) Stack() component.StackWithResources {
return component.StackWithResources{
Stack: component.Stack{
Dir: barrel.FilesystemBase,
Env: barrel.Malt.Environment,
},
Resources: barrelResources,
ContextPath: filepath.Join("barrel"),
EnvPath: filepath.Join("barrel.env"),
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": barrel.Malt.Config.DockerNetworkName,
"SLUG": barrel.Slug,
"VIRTUAL_HOST": barrel.Domain(),
"HTTPS_ENABLED": barrel.Malt.Config.HTTPSEnabledEnv(),
"DATA_PATH": filepath.Join(barrel.FilesystemBase, "data"),
"RUNTIME_DIR": barrel.Malt.Config.RuntimeDir(),
"GLOBAL_AUTHORIZED_KEYS_FILE": barrel.Malt.Config.GlobalAuthorizedKeysFile,
},
MakeDirs: []string{"data", ".composer"},
TouchFiles: []string{
filepath.Join("data", "authorized_keys"),
},
}
}

View file

@ -0,0 +1,43 @@
package bookkeeping
import (
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
)
// Bookkeeping provides instance bookkeeping
type Bookkeeping struct {
ingredient.Base
}
// Save saves this instance in the bookkeeping table
func (bk *Bookkeeping) Save() error {
sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable)
if err != nil {
return err
}
// it has never been created => we need to create it in the database
if bk.Instance.Created.IsZero() {
return sdb.Create(&bk.Instance).Error
}
// Update based on the primary key!
return sdb.Where("pk = ?", bk.Instance.Pk).Updates(&bk.Instance).Error
}
// Delete deletes this instance from the bookkeeping table
func (bk *Bookkeeping) Delete() error {
sdb, err := bk.Malt.SQL.QueryTable(false, models.InstanceTable)
if err != nil {
return err
}
// doesn't exist => nothing to delete
if bk.Instance.Created.IsZero() {
return nil
}
// delete it directly
return sdb.Delete(&bk.Instance).Error
}

View file

@ -1,13 +1,29 @@
package wisski
package info
import (
"time"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/tkw1536/goprogram/stream"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/drush"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/extras"
"golang.org/x/sync/errgroup"
)
type Info struct {
ingredient.Base
PHP *php.PHP
Barrel *barrel.Barrel
Locker *locker.Locker
Drush *drush.Drush
Prefixes *extras.Prefixes
Pathbuilder *extras.Pathbuilder
}
// WissKIInfo represents information about this WissKI Instance.
type WissKIInfo struct {
Time time.Time // Time this info was built
@ -33,14 +49,14 @@ type WissKIInfo struct {
Pathbuilders map[string]string // all the pathbuilders
}
// Info fetches information about this WissKI.
// Fetch fetches information about this WissKI.
// TODO: Rework this to be able to determine what kind of information is available.
func (wisski *WissKI) Info(quick bool) (info WissKIInfo, err error) {
func (wisski *Info) Fetch(quick bool) (info WissKIInfo, err error) {
var group errgroup.Group
wisski.infoQuick(&info, &group)
if !quick {
server, err := wisski.NewPHPServer()
server, err := wisski.PHP.NewServer()
if err == nil {
defer server.Close()
}
@ -51,45 +67,40 @@ func (wisski *WissKI) Info(quick bool) (info WissKIInfo, err error) {
return
}
func (wisski *WissKI) infoQuick(info *WissKIInfo, group *errgroup.Group) {
func (wisski *Info) infoQuick(info *WissKIInfo, group *errgroup.Group) {
info.Time = time.Now().UTC()
info.Slug = wisski.Slug
info.URL = wisski.URL().String()
group.Go(func() (err error) {
info.Running, err = wisski.Running()
info.Running, err = wisski.Barrel.Running()
return
})
group.Go(func() (err error) {
info.Locked = wisski.IsLocked()
info.Locked = wisski.Locker.Locked()
return
})
group.Go(func() (err error) {
info.LastRebuild, _ = wisski.LastRebuild()
info.LastRebuild, _ = wisski.Barrel.LastRebuild()
return
})
group.Go(func() (err error) {
info.LastUpdate, _ = wisski.LastUpdate()
info.LastUpdate, _ = wisski.Drush.LastUpdate()
return
})
group.Go(func() (err error) {
info.LastRebuild, _ = wisski.LastRebuild()
return
})
group.Go(func() (err error) {
info.NoPrefixes = wisski.NoPrefix()
info.NoPrefixes = wisski.Prefixes.NoPrefix()
return
})
}
func (wisski *WissKI) infoSlow(info *WissKIInfo, server *PHPServer, group *errgroup.Group) {
func (wisski *Info) infoSlow(info *WissKIInfo, server *php.Server, group *errgroup.Group) {
group.Go(func() (err error) {
info.Prefixes, _ = wisski.Prefixes(server)
info.Prefixes, _ = wisski.Prefixes.All(server)
return nil
})
@ -99,21 +110,12 @@ func (wisski *WissKI) infoSlow(info *WissKIInfo, server *PHPServer, group *errgr
})
group.Go(func() (err error) {
info.Pathbuilders, _ = wisski.AllPathbuilders(server)
info.Pathbuilders, _ = wisski.Pathbuilder.GetAll(server)
return nil
})
group.Go(func() (err error) {
info.LastCron, _ = wisski.LastCron(server)
info.LastCron, _ = wisski.Drush.LastCron(server)
return
})
}
// Running checks if this WissKI is currently running.
func (wisski *WissKI) Running() (bool, error) {
ps, err := wisski.Barrel().Ps(stream.FromNil())
if err != nil {
return false, err
}
return len(ps) > 0, nil
}

View file

@ -0,0 +1,48 @@
package ingredient
import (
"reflect"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/liquid"
)
// Ingredients represent a part of a WissKI instance.
// An Ingredient should be implemented as a pointer to a struct.
// Every ingredient must embed [Base] and should be initialized using [Init] inside a [lazy.Pool].
//
// By convention these are defined within their corresponding subpackage.
// This subpackage also contains all required resources.
type Ingredient interface {
// Name returns the name of this ingredient
// Name should be implemented by the [Base] struct.
Name() string
// getBase returns the underlying Base object of this Ingredient.
// It is used internally during initialization
getBase() *Base
}
// Base is embedded into every Ingredient
type Base struct {
name string // name is the name of this ingredient
*liquid.Liquid // the underlying liquid
}
//lint:ignore U1000 used to implement the private methods of [Component]
func (cb *Base) getBase() *Base {
return cb
}
// Init initializes a new Ingredient.
// Init is only intended to be used within a lazy.Pool[Ingredient,*Liquid].
func Init(ingredient Ingredient, liquid *liquid.Liquid) Ingredient {
base := ingredient.getBase() // pointer to a struct
base.Liquid = liquid
base.name = strings.ToLower(reflect.TypeOf(ingredient).Elem().Name())
return ingredient
}
func (cb Base) Name() string {
return cb.name
}

View file

@ -0,0 +1,44 @@
package locker
import (
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/tkw1536/goprogram/exit"
)
// Locker provides facitilites for locking this WissKI instance
type Locker struct {
ingredient.Base
}
var Locked = exit.Error{
Message: "WissKI Instance is locked for administrative operations",
ExitCode: exit.ExitGeneric,
}
// TryLock attemps to lock this WissKI and returns if it suceeded
func (lock *Locker) TryLock() bool {
table, err := lock.Malt.SQL.QueryTable(true, models.LockTable)
if err != nil {
return false
}
result := table.FirstOrCreate(&models.Lock{}, models.Lock{Slug: lock.Slug})
return result.Error == nil && result.RowsAffected == 1
}
// TryUnlock attempts to unlock this WissKI and reports if it succeeded.
// An unlock can only
func (lock *Locker) TryUnlock() bool {
table, err := lock.Malt.SQL.QueryTable(true, models.LockTable)
if err != nil {
return false
}
result := table.Where("slug = ?", lock.Slug).Delete(&models.Lock{})
return result.Error == nil && result.RowsAffected == 1
}
// Unlock unlocks this WissKI, ignoring any error.
func (lock *Locker) Unlock() {
lock.TryUnlock()
}

View file

@ -0,0 +1,15 @@
package locker
import "github.com/FAU-CDI/wisski-distillery/internal/models"
// Locked checks if this WissKI is currently locked.
func (lock *Locker) Locked() (locked bool) {
table, err := lock.SQL.QueryTable(true, models.LockTable)
if err != nil {
return false
}
// check if this instance is locked
table.Select("count(*) > 0").Where("slug = ?", lock.Slug).Find(&locked)
return
}

View file

@ -0,0 +1,43 @@
package mstore
import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/tkw1536/goprogram/lib/collection"
)
// MStore implements metadata storage for this WissKI
type MStore struct {
ingredient.Base
*meta.Storage
}
// For is a Store for the provided value
type For[Value any] meta.Key
func (f For[Value]) Get(m *MStore) (value Value, err error) {
err = m.Storage.Get(meta.Key(f), &value)
return
}
func (f For[Value]) GetAll(m *MStore) (values []Value, err error) {
err = m.Storage.GetAll(meta.Key(f), func(index, total int) any {
if values == nil {
values = make([]Value, total)
}
return &values[index]
})
return values, err
}
func (f For[Value]) Set(m *MStore, value Value) error {
return m.Storage.Set(meta.Key(f), value)
}
func (f For[Value]) SetAll(m *MStore, values ...Value) error {
return m.Storage.SetAll(meta.Key(f), collection.AsAny(values)...)
}
func (f For[Value]) Delete(m *MStore) error {
return m.Storage.Delete(meta.Key(f))
}

View file

@ -0,0 +1,44 @@
package extras
import (
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php"
"golang.org/x/exp/slices"
)
type Pathbuilder struct {
ingredient.Base
PHP *php.PHP
}
//go:embed pathbuilder.php
var pathbuilderPHP string
// All returns the ids of all pathbuilders in consistent order.
//
// server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) All(server *php.Server) (ids []string, err error) {
err = pathbuilder.PHP.ExecScript(server, &ids, pathbuilderPHP, "all_list")
slices.Sort(ids)
return
}
// Get returns a single pathbuilder as xml.
// If it does not exist, it returns the empty string and nil error.
//
// server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) Get(server *php.Server, id string) (xml string, err error) {
err = pathbuilder.PHP.ExecScript(server, &xml, pathbuilderPHP, "one_xml", id)
return
}
// GetAll returns all pathbuilders serialized as xml
//
// server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) GetAll(server *php.Server) (pathbuilders map[string]string, err error) {
err = pathbuilder.PHP.ExecScript(server, &pathbuilders, pathbuilderPHP, "all_xml")
return
}

View file

@ -1,47 +1,57 @@
package wisski
package extras
import (
"bufio"
"path/filepath"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/tkw1536/goprogram/lib/collection"
_ "embed"
)
// NoPrefix checks if this WissKI instance is excluded from generating prefixes.
// TODO: Move this to the database!
func (wisski *WissKI) NoPrefix() bool {
return fsx.IsFile(wisski.Core.Environment, filepath.Join(wisski.FilesystemBase, "prefixes.skip"))
// Prefixes implements reading and writing prefix
type Prefixes struct {
ingredient.Base
PHP *php.PHP
MStore *mstore.MStore
}
//go:embed php/list_uri_prefixes.php
// NoPrefix checks if this WissKI instance is excluded from generating prefixes.
// TODO: Move this to the database!
func (prefixes *Prefixes) NoPrefix() bool {
return fsx.IsFile(prefixes.Malt.Environment, filepath.Join(prefixes.FilesystemBase, "prefixes.skip"))
}
//go:embed prefixes.php
var listURIPrefixesPHP string
// Prefixes returns the prefixes applying to this WissKI
// All returns the prefixes applying to this WissKI
//
// server is an optional server to fetch prefixes from.
// server may be nil.
func (wisski *WissKI) Prefixes(server *PHPServer) ([]string, error) {
prefixes, err := wisski.dbPrefixes(server)
func (prefixes *Prefixes) All(server *php.Server) ([]string, error) {
uris, err := prefixes.database(server)
if err != nil {
return nil, err
}
prefixes2, err := wisski.filePrefixes()
uris2, err := prefixes.filePrefixes()
if err != nil {
return nil, err
}
return append(prefixes, prefixes2...), nil
return append(uris, uris2...), nil
}
func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err error) {
func (wisski *Prefixes) database(server *php.Server) (prefixes []string, err error) {
// get all the ugly prefixes
err = wisski.ExecPHPScript(server, &prefixes, listURIPrefixesPHP, "list_prefixes")
err = wisski.PHP.ExecScript(server, &prefixes, listURIPrefixesPHP, "list_prefixes")
if err != nil {
return nil, err
}
@ -52,7 +62,7 @@ func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err erro
})
// load the list of blocked prefixes
blocks, err := wisski.blockedPrefixes()
blocks, err := wisski.blocked()
if err != nil {
return nil, err
}
@ -61,9 +71,10 @@ func (wisski *WissKI) dbPrefixes(server *PHPServer) (prefixes []string, err erro
return collection.Filter(prefixes, func(uri string) bool { return !hasAnyPrefix(uri, blocks) }), nil
}
func (wisski *WissKI) blockedPrefixes() ([]string, error) {
func (prefixes *Prefixes) blocked() ([]string, error) {
// open the resolver block file
file, err := wisski.Core.Environment.Open(wisski.Core.Config.SelfResolverBlockFile)
// TODO: move this to the distillery
file, err := prefixes.Malt.Environment.Open(prefixes.Malt.Config.SelfResolverBlockFile)
if err != nil {
return nil, err
}
@ -98,13 +109,13 @@ func hasAnyPrefix(candidate string, prefixes []string) bool {
)
}
func (wisski *WissKI) filePrefixes() (prefixes []string, err error) {
func (wisski *Prefixes) filePrefixes() (prefixes []string, err error) {
path := filepath.Join(wisski.FilesystemBase, "prefixes")
if !fsx.IsFile(wisski.Core.Environment, path) {
if !fsx.IsFile(wisski.Malt.Environment, path) {
return nil, nil
}
file, err := wisski.Core.Environment.Open(path)
file, err := wisski.Malt.Environment.Open(path)
if err != nil {
return nil, err
}
@ -127,18 +138,18 @@ func (wisski *WissKI) filePrefixes() (prefixes []string, err error) {
// CACHING
var prefix = meta.StorageFor[string]("prefix")
var prefix = mstore.For[string]("prefix")
// Prefixes returns the cached prefixes from the given instance
func (wisski *WissKI) PrefixesCached() (results []string, err error) {
return prefix(wisski.storage()).GetAll()
func (wisski *Prefixes) PrefixesCached() (results []string, err error) {
return prefix.GetAll(wisski.MStore)
}
// UpdatePrefixes updates the cached prefixes of this instance
func (wisski *WissKI) UpdatePrefixes() error {
prefixes, err := wisski.Prefixes(nil)
func (wisski *Prefixes) UpdatePrefixes() error {
prefixes, err := wisski.All(nil)
if err != nil {
return err
}
return prefix(wisski.storage()).SetAll(prefixes...)
return prefix.SetAll(wisski.MStore, prefixes...)
}

View file

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

View file

@ -0,0 +1,58 @@
package php
import (
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
)
type PHP struct {
ingredient.Base
Barrel *barrel.Barrel
}
// ExecScript executes the PHP code as a script on the given server.
// When server is nil, creates a new server and automatically closes it after execution.
// Calling this function repeatedly with server = nil is inefficient.
//
// The script should define a function called entrypoint, and may define additional functions.
//
// Code must start with "<?php" and may not contain a closing tag.
// Code is expected not to mess with PHPs output buffer.
// Code should not contain user input.
// Code breaking these conventions may or may not result in an error.
//
// It's arguments are encoded as json using [json.Marshal] and decoded within php.
//
// The return value of the function is again marshaled with json and returned to the caller.
func (php *PHP) ExecScript(server *Server, value any, code string, entrypoint string, args ...any) (err error) {
if server == nil {
server, err = php.NewServer()
if err != nil {
return
}
defer server.Close()
}
if code != "" {
if err := server.MarshalEval(nil, strings.TrimPrefix(code, "<?php")); err != nil {
return err
}
}
return server.MarshalCall(value, entrypoint, args...)
}
func (php *PHP) EvalCode(server *Server, value any, code string) (err error) {
if server == nil {
server, err = php.NewServer()
if err != nil {
return
}
defer server.Close()
}
return server.MarshalEval(value, code)
}

View file

@ -1,4 +1,4 @@
package wisski
package php
import (
"context"
@ -21,50 +21,50 @@ import (
var (
errPHPInit = "Unable to initialize"
errPHPMarshal = "Marshal failed"
errPHPInvalid = PHPServerError{Message: "Invalid code to execute"}
errPHPInvalid = ServerError{Message: "Invalid code to execute"}
errPHPReceive = "Failed to receive response"
errPHPClosed = PHPServerError{Message: "Server closed"}
errPHPClosed = ServerError{Message: "Server closed"}
)
// PHPError represents an error during PHPServer logic
type PHPServerError struct {
type ServerError struct {
Message string
Err error
}
func (err PHPServerError) Unwrap() error {
func (err ServerError) Unwrap() error {
return err.Err
}
func (err PHPServerError) Error() string {
func (err ServerError) Error() string {
if err.Err == nil {
return fmt.Sprintf("PHPServer: %s", err.Message)
}
return fmt.Sprintf("PHPServer: %s: %s", err.Message, err.Err)
}
// PHPThrowable represents an error during php code
type PHPThrowable string
// Throwable represents an error during php code
type Throwable string
func (throwable PHPThrowable) Error() string {
func (throwable Throwable) Error() string {
return string(throwable)
}
// NewPHPServer returns a new server that can execute code within this distillery.
// NewServer returns a new server that can execute code within this distillery.
// When err == nil, the caller must call server.Close().
//
// See [PHPServer].
func (wisski *WissKI) NewPHPServer() (*PHPServer, error) {
func (php *PHP) NewServer() (*Server, error) {
// create input and output pipes
ir, iw, err := os.Pipe()
if err != nil {
return nil, PHPServerError{errPHPInit, err}
return nil, ServerError{errPHPInit, err}
}
or, ow, err := os.Pipe()
if err != nil {
ir.Close()
iw.Close()
return nil, PHPServerError{errPHPInit, err}
return nil, ServerError{errPHPInit, err}
}
// create a context to close the server
@ -83,22 +83,22 @@ func (wisski *WissKI) NewPHPServer() (*PHPServer, error) {
// start the server
io := stream.NewIOStream(ow, nil, ir, 0)
wisski.Shell(io, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", serverPHP}))
php.Barrel.Shell(io, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", serverPHP}))
}()
// return the seerver
return &PHPServer{
return &Server{
in: iw,
out: or,
c: context,
}, nil
}
// PHPServer represents a server that executes code within a distillery.
// Server represents a server that executes code within a distillery.
// A typical use-case is to define functions using [MarshalEval], and then call those functions [MarshalCall].
//
// A nil PHPServer will return [ErrServerBroken] on every function call.
type PHPServer struct {
// A nil Server will return [ErrServerBroken] on every function call.
type Server struct {
m sync.Mutex
in io.WriteCloser
@ -113,7 +113,7 @@ type PHPServer struct {
// as such any functions defined will remain in server memory.
//
// When an exception is thrown by the PHP Code, error is not nil, and dest remains unchanged.
func (server *PHPServer) MarshalEval(value any, code string) error {
func (server *Server) MarshalEval(value any, code string) error {
server.m.Lock()
defer server.m.Unlock()
@ -125,7 +125,7 @@ func (server *PHPServer) MarshalEval(value any, code string) error {
// marshal the code, and send it to the server
bytes, err := json.Marshal(code)
if err != nil {
return PHPServerError{Message: errPHPMarshal, Err: err}
return ServerError{Message: errPHPMarshal, Err: err}
}
// send it to the server
@ -134,19 +134,19 @@ func (server *PHPServer) MarshalEval(value any, code string) error {
// read the next line (as a response)
data, err := nobufio.ReadLine(server.out)
if err != nil {
return PHPServerError{Message: errPHPReceive, Err: err}
return ServerError{Message: errPHPReceive, Err: err}
}
// read whatever we received
var received [2]json.RawMessage
if err := json.Unmarshal([]byte(data), &received); err != nil {
return PHPServerError{Message: errPHPMarshal, Err: err}
return ServerError{Message: errPHPMarshal, Err: err}
}
// check if there was an error
var errString string
if err := json.Unmarshal(received[1], &errString); err == nil && errString != "" {
return PHPThrowable(errString)
return Throwable(errString)
}
// special case: no return value => no unmarshaling needed
@ -159,7 +159,7 @@ func (server *PHPServer) MarshalEval(value any, code string) error {
}
// Eval is like [MarshalEval], but returns the value as an any
func (server *PHPServer) Eval(code string) (value any, err error) {
func (server *Server) Eval(code string) (value any, err error) {
err = server.MarshalEval(&value, code)
return
}
@ -168,18 +168,18 @@ func (server *PHPServer) Eval(code string) (value any, err error) {
// Arguments are sent to php using json Marshal, and are 'json_decode'd on the php side.
//
// Return values are received as in [MarshalEval].
func (server *PHPServer) MarshalCall(value any, function string, args ...any) error {
func (server *Server) MarshalCall(value any, function string, args ...any) error {
// marshal a code for the call
userFunction, err := marshalPHP(function)
if err != nil {
return PHPServerError{Message: errPHPMarshal, Err: err}
return ServerError{Message: errPHPMarshal, Err: err}
}
userFunctionArgs := "[]"
if len(args) > 0 {
userFunctionArgs, err = marshalPHP(args)
if err != nil {
return PHPServerError{Message: errPHPMarshal, Err: err}
return ServerError{Message: errPHPMarshal, Err: err}
}
}
code := "return call_user_func_array(" + userFunction + "," + userFunctionArgs + ");"
@ -189,7 +189,7 @@ func (server *PHPServer) MarshalCall(value any, function string, args ...any) er
}
// Call is like [MarshalCall] but returns the return value of the function as an any
func (server *PHPServer) Call(function string, args ...any) (value any, err error) {
func (server *Server) Call(function string, args ...any) (value any, err error) {
err = server.MarshalCall(&value, function, args...)
return
}
@ -233,7 +233,7 @@ func marshalPHP(data any) (string, error) {
}
// Close closes this server and prevents any further code from being run.
func (server *PHPServer) Close() error {
func (server *Server) Close() error {
server.m.Lock()
defer server.m.Unlock()
@ -248,51 +248,7 @@ func (server *PHPServer) Close() error {
return nil
}
// ExecPHPScript executes the PHP code as a script on the given server.
// When server is nil, creates a new server and automatically closes it after execution.
// Calling this function repeatedly with server = nil is inefficient.
//
// The script should define a function called entrypoint, and may define additional functions.
//
// Code must start with "<?php" and may not contain a closing tag.
// Code is expected not to mess with PHPs output buffer.
// Code should not contain user input.
// Code breaking these conventions may or may not result in an error.
//
// It's arguments are encoded as json using [json.Marshal] and decoded within php.
//
// The return value of the function is again marshaled with json and returned to the caller.
func (wisski *WissKI) ExecPHPScript(server *PHPServer, value any, code string, entrypoint string, args ...any) (err error) {
if server == nil {
server, err = wisski.NewPHPServer()
if err != nil {
return
}
defer server.Close()
}
if code != "" {
if err := server.MarshalEval(nil, strings.TrimPrefix(code, "<?php")); err != nil {
return err
}
}
return server.MarshalCall(value, entrypoint, args...)
}
func (wisski *WissKI) EvalPHPCode(server *PHPServer, value any, code string) (err error) {
if server == nil {
server, err = wisski.NewPHPServer()
if err != nil {
return
}
defer server.Close()
}
return server.MarshalEval(value, code)
}
//go:embed php/server.php
//go:embed server.php
var serverPHP string
// pre-process the server.php code to make it shorter

View file

@ -0,0 +1,40 @@
package reserve
import (
"embed"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
)
// Reserve implements reserving a WissKI Instance
// TODO: This should be integrated into the bookkeeping table.
type Reserve struct {
ingredient.Base
}
//go:embed all:reserve reserve.env
var reserveResources embed.FS
// Stack returns a stack representing the reserve instance
func (reserve *Reserve) Stack() component.StackWithResources {
return component.StackWithResources{
Stack: component.Stack{
Dir: reserve.FilesystemBase,
Env: reserve.Malt.Environment,
},
Resources: reserveResources,
ContextPath: filepath.Join("reserve"),
EnvPath: filepath.Join("reserve.env"),
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": reserve.Malt.Config.DockerNetworkName,
"SLUG": reserve.Slug,
"VIRTUAL_HOST": reserve.Domain(),
"HTTPS_ENABLED": reserve.Malt.Config.HTTPSEnabledEnv(),
},
}
}

View file

@ -0,0 +1,65 @@
package wisski
import (
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/liquid"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
"github.com/tkw1536/goprogram/lib/collection"
)
//
// ==== init ====
//
func (wisski *WissKI) init() {
wisski.poolInit.Do(func() {
wisski.pool.Init = ingredient.Init
})
}
//
// ==== registration ====
//
// manual initializes a component from the provided distillery.
func manual[I ingredient.Ingredient](init func(ingredient I)) initFunc {
return func(context ctx) ingredient.Ingredient {
return lazy.Make(context, init)
}
}
// use is like r, but does not provided additional initialization
func auto[I ingredient.Ingredient](context ctx) ingredient.Ingredient {
return lazy.Make[ingredient.Ingredient, I](context, nil)
}
// register returns all components of the distillery
func (wisski *WissKI) register(context ctx) []ingredient.Ingredient {
return collection.MapSlice(
wisski.allIngredients(),
func(f initFunc) ingredient.Ingredient {
return f(context)
},
)
}
// ctx is a context for component initialization
type ctx = *lazy.PoolContext[ingredient.Ingredient]
//
// ==== export ====
//
// export is a convenience function to export a single component
func export[I ingredient.Ingredient](wisski *WissKI) I {
wisski.init()
return lazy.ExportComponent[ingredient.Ingredient, *liquid.Liquid, I](&wisski.pool, &wisski.Liquid, wisski.register)
}
//lint:ignore U1000 for future use
func exportAll[I ingredient.Ingredient](wisski *WissKI) []I {
wisski.init()
return lazy.ExportComponents[ingredient.Ingredient, *liquid.Liquid, I](&wisski.pool, &wisski.Liquid, wisski.register)
}
type initFunc = func(context ctx) ingredient.Ingredient

View file

@ -1,4 +1,4 @@
package wisski
package liquid
import (
"fmt"
@ -6,20 +6,20 @@ import (
)
// Domain returns the full domain name of this WissKI
func (wisski WissKI) Domain() string {
return fmt.Sprintf("%s.%s", wisski.Slug, wisski.Core.Config.DefaultDomain)
func (liquid *Liquid) Domain() string {
return fmt.Sprintf("%s.%s", liquid.Slug, liquid.Malt.Config.DefaultDomain)
}
// URL returns the public URL of this instance
func (wisski WissKI) URL() *url.URL {
func (liquid *Liquid) URL() *url.URL {
// setup domain and path
url := &url.URL{
Host: wisski.Domain(),
Host: liquid.Domain(),
Path: "/",
}
// use http or https scheme depending on if the distillery has it enabled
if wisski.Core.Config.HTTPSEnabled() {
if liquid.Malt.Config.HTTPSEnabled() {
url.Scheme = "https"
} else {
url.Scheme = "http"

View file

@ -0,0 +1,16 @@
// Package liquid provides Liquid
package liquid
import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt"
"github.com/FAU-CDI/wisski-distillery/internal/models"
)
// Liquid is the core of a WissKI Instance and used in every ingredient.
type Liquid struct {
*malt.Malt
models.Instance
DrupalUsername string
DrupalPassword string
}

View file

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

View file

@ -1,47 +0,0 @@
package wisski
import (
"errors"
"github.com/FAU-CDI/wisski-distillery/internal/models"
)
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.SQL.QueryTable(true, models.LockTable)
if err != nil {
return ErrLocked
}
result := table.FirstOrCreate(&models.Lock{}, models.Lock{Slug: wisski.Slug})
locked := result.Error == nil && result.RowsAffected == 1
if !locked {
return ErrLocked
}
return nil
}
func (wisski *WissKI) IsLocked() (locked bool) {
table, err := wisski.SQL.QueryTable(true, models.LockTable)
if err != nil {
return false
}
// check if this instance is locked
table.Select("count(*) > 0").Where("slug = ?", wisski.Slug).Find(&locked)
return
}
// Unlock unlocks this WissKI instance and returns if it succeeded
func (wisski WissKI) Unlock() bool {
table, err := wisski.SQL.QueryTable(true, models.LockTable)
if err != nil {
return false
}
result := table.Where("slug = ?", wisski.Slug).Delete(&models.Lock{})
return result.Error == nil && result.RowsAffected == 1
}

View file

@ -1,7 +0,0 @@
package wisski
import "github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
func (wisski *WissKI) storage() *meta.Storage {
return wisski.Meta.Storage(wisski.Slug)
}

View file

@ -1,36 +0,0 @@
package wisski
import (
_ "embed"
"golang.org/x/exp/slices"
)
//go:embed php/export_pathbuilder.php
var exportPathbuilderPHP string
// Pathbuilders returns the ids of all pathbuilders in consistent order.
//
// server is the server to fetch the pathbuilders from, any may be nil.
func (wisski *WissKI) Pathbuilders(server *PHPServer) (ids []string, err error) {
err = wisski.ExecPHPScript(server, &ids, exportPathbuilderPHP, "all_list")
slices.Sort(ids)
return
}
// Pathbuilder returns a single pathbuilder as xml.
// If it does not exist, it returns the empty string and nil error.
//
// server is the server to fetch the pathbuilders from, any may be nil.
func (wisski *WissKI) Pathbuilder(server *PHPServer, id string) (xml string, err error) {
err = wisski.ExecPHPScript(server, &xml, exportPathbuilderPHP, "one_xml", id)
return
}
// AllPathbuilders returns all pathbuilders serialized as xml
//
// server is the server to fetch the pathbuilders from, any may be nil.
func (wisski *WissKI) AllPathbuilders(server *PHPServer) (pathbuilders map[string]string, err error) {
err = wisski.ExecPHPScript(server, &pathbuilders, exportPathbuilderPHP, "all_xml")
return
}

View file

@ -1,17 +0,0 @@
package wisski
import (
_ "embed"
)
//go:embed php/settings.php
var settingsPHP string
func (wisski *WissKI) GetSettingsPHP(server *PHPServer, key string) (value any, err error) {
err = wisski.ExecPHPScript(server, &value, settingsPHP, "get_setting", key)
return
}
func (wisski *WissKI) SetSettingsPHP(server *PHPServer, key string, value any) error {
return wisski.ExecPHPScript(server, nil, settingsPHP, "set_setting", key, value)
}

View file

@ -1,55 +0,0 @@
package wisski
import (
"errors"
"strings"
"github.com/alessio/shellescape"
"github.com/tkw1536/goprogram/stream"
)
// Provision provisions an instance, assuming that the required databases already exist.
func (wisski *WissKI) Provision(io stream.IOStream) error {
// build the container
if err := wisski.Build(io, false); err != nil {
return err
}
provisionParams := []string{
wisski.Domain(),
wisski.SqlDatabase,
wisski.SqlUsername,
wisski.SqlPassword,
wisski.GraphDBRepository,
wisski.GraphDBUsername,
wisski.GraphDBPassword,
wisski.DrupalUsername,
wisski.DrupalPassword,
"", // TODO: DrupalVersion
"", // TODO: WissKIVersion
}
// escape the parameter
for i, param := range provisionParams {
provisionParams[i] = shellescape.Quote(param)
}
// figure out the provision script
// TODO: Move the provision script into the control plane!
provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ")
code, err := wisski.Barrel().Run(io, true, "barrel", "/bin/bash", "-c", provisionScript)
if err != nil {
return err
}
if code != 0 {
return errors.New("unable to run provision script")
}
return nil
}

View file

@ -1,8 +0,0 @@
package wisski
import "github.com/FAU-CDI/wisski-distillery/internal/models"
// Snapshots returns the list of snapshots of this WissKI
func (wisski *WissKI) Snapshots() (snapshots []models.Export, err error) {
return wisski.ExporterLog.For(wisski.Slug)
}

View file

@ -1,127 +0,0 @@
package wisski
import (
"embed"
"path/filepath"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/tkw1536/goprogram/stream"
)
//go:embed all:instances/barrel instances/barrel.env
var barrelResources embed.FS
// Barrel returns a stack representing the running WissKI Instance
func (wisski *WissKI) Barrel() component.StackWithResources {
return component.StackWithResources{
Stack: component.Stack{
Dir: wisski.FilesystemBase,
Env: wisski.Core.Environment,
},
Resources: barrelResources,
ContextPath: filepath.Join("instances", "barrel"),
EnvPath: filepath.Join("instances", "barrel.env"),
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": wisski.Core.Config.DockerNetworkName,
"SLUG": wisski.Slug,
"VIRTUAL_HOST": wisski.Domain(),
"HTTPS_ENABLED": wisski.Core.Config.HTTPSEnabledEnv(),
"DATA_PATH": filepath.Join(wisski.FilesystemBase, "data"),
"RUNTIME_DIR": wisski.Core.Config.RuntimeDir(),
"GLOBAL_AUTHORIZED_KEYS_FILE": wisski.Core.Config.GlobalAuthorizedKeysFile,
},
MakeDirs: []string{"data", ".composer"},
TouchFiles: []string{
filepath.Join("data", "authorized_keys"),
},
}
}
// TODO: Move this to time.Time
var lastRebuild = meta.StorageFor[int64]("lastRebuild")
func (wisski *WissKI) LastRebuild() (t time.Time, err error) {
epoch, err := lastRebuild(wisski.storage()).Get()
if err == meta.ErrMetadatumNotSet {
return t, nil
}
if err != nil {
return t, err
}
// and turn it into time!
return time.Unix(epoch, 0), nil
}
func (wisski *WissKI) setLastRebuild() error {
return lastRebuild(wisski.storage()).Set(time.Now().Unix())
}
// Build builds or rebuilds the barel connected to this instance.
//
// It also logs the current time into the metadata belonging to this instance.
func (wisski *WissKI) Build(stream stream.IOStream, start bool) error {
if err := wisski.TryLock(); err != nil {
return err
}
defer wisski.Unlock()
barrel := wisski.Barrel()
var context component.InstallationContext
{
err := barrel.Install(stream, context)
if err != nil {
return err
}
}
{
err := barrel.Update(stream, start)
if err != nil {
return err
}
}
// store the current last rebuild
return wisski.setLastRebuild()
}
//go:embed all:instances/reserve instances/reserve.env
var reserveResources embed.FS
// Reserve returns a stack representing the reserve instance
func (wisski *WissKI) Reserve() component.StackWithResources {
return component.StackWithResources{
Stack: component.Stack{
Dir: wisski.FilesystemBase,
Env: wisski.Core.Environment,
},
Resources: reserveResources,
ContextPath: filepath.Join("instances", "reserve"),
EnvPath: filepath.Join("instances", "reserve.env"),
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": wisski.Core.Config.DockerNetworkName,
"SLUG": wisski.Slug,
"VIRTUAL_HOST": wisski.Domain(),
"HTTPS_ENABLED": wisski.Core.Config.HTTPSEnabledEnv(),
},
}
}
// Shell executes a shell command inside the instance.
func (wisski *WissKI) Shell(io stream.IOStream, argv ...string) (int, error) {
return wisski.Barrel().Exec(io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...)
}

View file

@ -1,47 +0,0 @@
package wisski
import (
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/stream"
)
var errBlindUpdateFailed = exit.Error{
Message: "Failed to run blind update script for instance %q: exited with code %s",
ExitCode: exit.ExitGeneric,
}
// BlinUpdate performs a blind update of the given instance
func (wisski *WissKI) BlindUpdate(io stream.IOStream) error {
code, err := wisski.Shell(io, "/runtime/blind_update.sh")
if err != nil {
return errBlindUpdateFailed.WithMessageF(wisski.Slug, environment.ExecCommandError)
}
if code != 0 {
return errBlindUpdateFailed.WithMessageF(wisski.Slug, code)
}
return wisski.setLastUpdate()
}
var lastUpdate = meta.StorageFor[int64]("lastUpdate")
func (wisski *WissKI) LastUpdate() (t time.Time, err error) {
epoch, err := lastUpdate(wisski.storage()).Get()
if err == meta.ErrMetadatumNotSet {
return t, nil
}
if err != nil {
return t, err
}
// and turn it into time!
return time.Unix(epoch, 0), nil
}
func (wisski *WissKI) setLastUpdate() error {
return lastUpdate(wisski.storage()).Set(time.Now().Unix())
}

View file

@ -2,60 +2,108 @@
package wisski
import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"sync"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/meta"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/drush"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/provisioner"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/bookkeeping"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/extras"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/reserve"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/liquid"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
)
// WissKI represents a single WissKI Instance
// WissKI represents a single WissKI Instance.
// A WissKI may not be copied
type WissKI struct {
models.Instance // whatever is stored inside the underlying instance
liquid.Liquid
// Drupal credentials - not stored in the database
DrupalUsername string
DrupalPassword string
// references to components!
Core component.Still
Meta *meta.Meta
TS *triplestore.Triplestore
SQL *sql.SQL
ExporterLog *logger.Logger
poolInit sync.Once
pool lazy.Pool[ingredient.Ingredient, *liquid.Liquid]
}
// Save saves this instance in the bookkeeping table
func (wisski *WissKI) Save() error {
db, err := wisski.SQL.QueryTable(false, models.InstanceTable)
if err != nil {
return err
}
//
// PUBLIC INGREDIENT GETTERS
//
// it has never been created => we need to create it in the database
if wisski.Instance.Created.IsZero() {
return db.Create(&wisski.Instance).Error
}
// Update based on the primary key!
return db.Where("pk = ?", wisski.Instance.Pk).Updates(&wisski.Instance).Error
func (wisski *WissKI) Locker() *locker.Locker {
return export[*locker.Locker](wisski)
}
// Delete deletes this instance from the bookkeeping table
func (wisski *WissKI) Delete() error {
db, err := wisski.SQL.QueryTable(false, models.InstanceTable)
if err != nil {
return err
}
// doesn't exist => nothing to delete
if wisski.Instance.Created.IsZero() {
return nil
}
// delete it directly
return db.Delete(&wisski.Instance).Error
func (wisski *WissKI) Reserve() *reserve.Reserve {
return export[*reserve.Reserve](wisski)
}
func (wisski *WissKI) Barrel() *barrel.Barrel {
return export[*barrel.Barrel](wisski)
}
func (wisski *WissKI) Provisioner() *provisioner.Provisioner {
return export[*provisioner.Provisioner](wisski)
}
func (wisski *WissKI) PHP() *php.PHP {
return export[*php.PHP](wisski)
}
func (wisski *WissKI) Bookkeeping() *bookkeeping.Bookkeeping {
return export[*bookkeeping.Bookkeeping](wisski)
}
func (wisski *WissKI) Drush() *drush.Drush {
return export[*drush.Drush](wisski)
}
func (wisski *WissKI) Prefixes() *extras.Prefixes {
return export[*extras.Prefixes](wisski)
}
func (wisski *WissKI) Settings() *extras.Settings {
return export[*extras.Settings](wisski)
}
func (wisski *WissKI) Pathbuilder() *extras.Pathbuilder {
return export[*extras.Pathbuilder](wisski)
}
func (wisski *WissKI) Info() *info.Info {
return export[*info.Info](wisski)
}
//
// All components
// THESE SHOULD NEVER BE CALLED DIRECTLY
//
func (wisski *WissKI) allIngredients() []initFunc {
return []initFunc{
// core bits
auto[*locker.Locker],
manual(func(m *mstore.MStore) {
m.Storage = wisski.Malt.Meta.Storage(wisski.Slug)
}),
// php
auto[*php.PHP],
auto[*extras.Prefixes],
auto[*extras.Settings],
auto[*extras.Pathbuilder],
// info
auto[*info.Info],
// stacks
auto[*barrel.Barrel],
auto[*bookkeeping.Bookkeeping],
auto[*provisioner.Provisioner],
auto[*drush.Drush],
auto[*reserve.Reserve],
}
}