Move wisski instance code to separate package
This commit is contained in:
parent
7c3c84e116
commit
063f3f9b7d
67 changed files with 533 additions and 409 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
65
internal/component/instances/create.go
Normal file
65
internal/component/instances/create.go
Normal 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
|
||||
}
|
||||
|
|
@ -5,9 +5,12 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/meta"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/triplestore"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
||||
"github.com/tkw1536/goprogram/exit"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
|
@ -17,8 +20,10 @@ import (
|
|||
type Instances struct {
|
||||
component.ComponentBase
|
||||
|
||||
TS *triplestore.Triplestore
|
||||
SQL *sql.SQL
|
||||
TS *triplestore.Triplestore
|
||||
SQL *sql.SQL
|
||||
Meta *meta.Meta
|
||||
SnapshotsLog *snapshotslog.SnapshotsLog
|
||||
}
|
||||
|
||||
func (Instances) Name() string {
|
||||
|
|
@ -37,40 +42,53 @@ var errSQL = exit.Error{
|
|||
ExitCode: exit.ExitGeneric,
|
||||
}
|
||||
|
||||
// Instance is a convenience function to return an instance based on a model slug.
|
||||
// When the instance does not exist, returns nil.
|
||||
func (instances *Instances) Instance(instance models.Instance) *WissKI {
|
||||
i, err := instances.WissKI(instance.Slug)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &i
|
||||
// use uses the non-nil wisski instance with this instances
|
||||
func (instances *Instances) use(wisski *wisski.WissKI) {
|
||||
wisski.Core = instances.Core
|
||||
wisski.SQL = instances.SQL
|
||||
wisski.TS = instances.TS
|
||||
wisski.Meta = instances.Meta
|
||||
wisski.SnapshotsLog = instances.SnapshotsLog
|
||||
}
|
||||
|
||||
// WissKI returns the WissKI with the provided slug, if it exists.
|
||||
// It the WissKI does not exist, returns ErrWissKINotFound.
|
||||
func (instances *Instances) WissKI(slug string) (i WissKI, err error) {
|
||||
func (instances *Instances) WissKI(slug string) (wissKI *wisski.WissKI, err error) {
|
||||
sql := instances.SQL
|
||||
if err := sql.WaitQueryTable(); err != nil {
|
||||
return i, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
table, err := sql.QueryTable(false, models.InstanceTable)
|
||||
if err != nil {
|
||||
return i, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create a struct
|
||||
wissKI = new(wisski.WissKI)
|
||||
|
||||
// find the instance by slug
|
||||
query := table.Where(&models.Instance{Slug: slug}).Find(&i.Instance)
|
||||
query := table.Where(&models.Instance{Slug: slug}).Find(&wissKI.Instance)
|
||||
switch {
|
||||
case query.Error != nil:
|
||||
return i, errSQL.WithMessageF(query.Error)
|
||||
return nil, errSQL.WithMessageF(query.Error)
|
||||
case query.RowsAffected == 0:
|
||||
return i, ErrWissKINotFound
|
||||
default:
|
||||
i.instances = instances
|
||||
return i, nil
|
||||
return nil, ErrWissKINotFound
|
||||
}
|
||||
|
||||
// use the wissKI instance
|
||||
instances.use(wissKI)
|
||||
return wissKI, nil
|
||||
}
|
||||
|
||||
// Instance is a convenience function to return an instance based on a model slug.
|
||||
// When the instance does not exist, returns nil.
|
||||
func (instances *Instances) Instance(instance models.Instance) *wisski.WissKI {
|
||||
wissKI, err := instances.WissKI(instance.Slug)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return wissKI
|
||||
}
|
||||
|
||||
// Has checks if a WissKI with the provided slug exists inside the database.
|
||||
|
|
@ -96,7 +114,7 @@ func (instances *Instances) Has(slug string) (ok bool, err error) {
|
|||
// All returns all instances of the WissKI Distillery in consistent order.
|
||||
//
|
||||
// There is no guarantee that this order remains identical between different api releases; however subsequent invocations are guaranteed to return the same order.
|
||||
func (instances *Instances) All() ([]WissKI, error) {
|
||||
func (instances *Instances) All() ([]*wisski.WissKI, error) {
|
||||
return instances.find(true, func(table *gorm.DB) *gorm.DB {
|
||||
return table
|
||||
})
|
||||
|
|
@ -104,14 +122,14 @@ func (instances *Instances) All() ([]WissKI, error) {
|
|||
|
||||
// WissKIs returns the WissKI instances with the provides slugs.
|
||||
// If a slug does not exist, it is omitted from the result.
|
||||
func (instances *Instances) WissKIs(slugs ...string) ([]WissKI, error) {
|
||||
func (instances *Instances) WissKIs(slugs ...string) ([]*wisski.WissKI, error) {
|
||||
return instances.find(true, func(table *gorm.DB) *gorm.DB {
|
||||
return table.Where("slug IN ?", slugs)
|
||||
})
|
||||
}
|
||||
|
||||
// Load is like All, except that when no slugs are provided, it calls All.
|
||||
func (instances *Instances) Load(slugs ...string) ([]WissKI, error) {
|
||||
func (instances *Instances) Load(slugs ...string) ([]*wisski.WissKI, error) {
|
||||
if len(slugs) == 0 {
|
||||
return instances.All()
|
||||
}
|
||||
|
|
@ -119,7 +137,7 @@ func (instances *Instances) Load(slugs ...string) ([]WissKI, error) {
|
|||
}
|
||||
|
||||
// find finds instances based on the provided query
|
||||
func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []WissKI, err error) {
|
||||
func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) {
|
||||
sql := instances.SQL
|
||||
if err := sql.WaitQueryTable(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -148,10 +166,11 @@ func (instances *Instances) find(order bool, query func(table *gorm.DB) *gorm.DB
|
|||
}
|
||||
|
||||
// make proper instances
|
||||
results = make([]WissKI, len(bks))
|
||||
results = make([]*wisski.WissKI, len(bks))
|
||||
for i, bk := range bks {
|
||||
results[i] = new(wisski.WissKI)
|
||||
results[i].Instance = bk
|
||||
results[i].instances = instances
|
||||
instances.use(results[i])
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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...)...)
|
||||
}
|
||||
44
internal/component/meta/meta.go
Normal file
44
internal/component/meta/meta.go
Normal 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]
|
||||
}
|
||||
14
internal/component/meta/provision.go
Normal file
14
internal/component/meta/provision.go
Normal 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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/snapshotslog"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
|
||||
|
|
@ -17,8 +18,9 @@ import (
|
|||
type Manager struct {
|
||||
component.ComponentBase
|
||||
|
||||
SQL *sql.SQL
|
||||
Instances *instances.Instances
|
||||
SQL *sql.SQL
|
||||
Instances *instances.Instances
|
||||
SnapshotsLog *snapshotslog.SnapshotsLog
|
||||
|
||||
Snapshotable []component.Snapshotable
|
||||
Backupable []component.Backupable
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package instances
|
||||
package wisski
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package instances
|
||||
package wisski
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
|
@ -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
7
internal/wisski/meta.go
Normal 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)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package instances
|
||||
package wisski
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package instances
|
||||
package wisski
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package instances
|
||||
package wisski
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -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...)
|
||||
}
|
||||
|
|
@ -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"
|
||||
55
internal/wisski/provision.go
Normal file
55
internal/wisski/provision.go
Normal 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
|
||||
}
|
||||
8
internal/wisski/snapshots.go
Normal file
8
internal/wisski/snapshots.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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...)...)
|
||||
}
|
||||
|
|
@ -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
60
internal/wisski/wisski.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue