Move instances into a separate component
This commit is contained in:
parent
233a51d4cd
commit
a8da3f70eb
46 changed files with 553 additions and 551 deletions
|
|
@ -33,7 +33,7 @@ var errBlindUpdateFailed = exit.Error{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bu blindUpdate) Run(context wisski_distillery.Context) error {
|
func (bu blindUpdate) Run(context wisski_distillery.Context) error {
|
||||||
instances, err := context.Environment.Instances(bu.Positionals.Slug...)
|
instances, err := context.Environment.Instances().Load(bu.Positionals.Slug...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ var errCronFailed = exit.Error{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr cron) Run(context wisski_distillery.Context) error {
|
func (cr cron) Run(context wisski_distillery.Context) error {
|
||||||
instances, err := context.Environment.Instances(cr.Positionals.Slug...)
|
instances, err := context.Environment.Instances().Load(cr.Positionals.Slug...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func (info) Description() wisski_distillery.Description {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i info) Run(context wisski_distillery.Context) error {
|
func (i info) Run(context wisski_distillery.Context) error {
|
||||||
instance, err := context.Environment.Instance(i.Positionals.Slug)
|
instance, err := context.Environment.Instances().WissKI(i.Positionals.Slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -34,11 +34,11 @@ func (i info) Run(context wisski_distillery.Context) error {
|
||||||
context.Printf("Base directory: %s\n", instance.FilesystemBase)
|
context.Printf("Base directory: %s\n", instance.FilesystemBase)
|
||||||
|
|
||||||
context.Printf("SQL Database: %s\n", instance.SqlDatabase)
|
context.Printf("SQL Database: %s\n", instance.SqlDatabase)
|
||||||
context.Printf("SQL Username: %s\n", instance.SqlUser)
|
context.Printf("SQL Username: %s\n", instance.SqlUsername)
|
||||||
context.Printf("SQL Password: %s\n", instance.SqlPassword)
|
context.Printf("SQL Password: %s\n", instance.SqlPassword)
|
||||||
|
|
||||||
context.Printf("GraphDB Repository: %s\n", instance.GraphDBRepository)
|
context.Printf("GraphDB Repository: %s\n", instance.GraphDBRepository)
|
||||||
context.Printf("GraphDB Username: %s\n", instance.GraphDBUser)
|
context.Printf("GraphDB Username: %s\n", instance.GraphDBUsername)
|
||||||
context.Printf("GraphDB Password: %s\n", instance.GraphDBPassword)
|
context.Printf("GraphDB Password: %s\n", instance.GraphDBPassword)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func (ls) Description() wisski_distillery.Description {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l ls) Run(context wisski_distillery.Context) error {
|
func (l ls) Run(context wisski_distillery.Context) error {
|
||||||
instances, err := context.Environment.Instances(l.Positionals.Slug...)
|
instances, err := context.Environment.Instances().Load(l.Positionals.Slug...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,12 @@ func (p provision) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// check that it doesn't already exist
|
// check that it doesn't already exist
|
||||||
logging.LogMessage(context.IOStream, "Provisioning new WissKI instance %s", slug)
|
logging.LogMessage(context.IOStream, "Provisioning new WissKI instance %s", slug)
|
||||||
if exists, err := dis.HasInstance(slug); err != nil || exists {
|
if exists, err := dis.Instances().Has(slug); err != nil || exists {
|
||||||
return errProvisionAlreadyExists.WithMessageF(slug)
|
return errProvisionAlreadyExists.WithMessageF(slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make it in-memory
|
// make it in-memory
|
||||||
instance, err := dis.NewInstance(slug)
|
instance, err := dis.Instances().Create(slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errProvisionGeneric.WithMessageF(slug, err)
|
return errProvisionGeneric.WithMessageF(slug, err)
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +63,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// Store in bookkeeping
|
// Store in bookkeeping
|
||||||
if err := logging.LogOperation(func() error {
|
if err := logging.LogOperation(func() error {
|
||||||
if err := instance.Update(); err != nil {
|
if err := instance.Save(); err != nil {
|
||||||
return errProvisionGeneric.WithMessageF(slug, err)
|
return errProvisionGeneric.WithMessageF(slug, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// create the sql
|
// create the sql
|
||||||
if err := logging.LogOperation(func() error {
|
if err := logging.LogOperation(func() error {
|
||||||
if err := dis.SQL().Provision(instance.SqlDatabase, instance.SqlUser, instance.SqlPassword); err != nil {
|
if err := dis.SQL().Provision(instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword); err != nil {
|
||||||
return errProvisionGeneric.WithMessageF(slug, err)
|
return errProvisionGeneric.WithMessageF(slug, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// create the triplestore
|
// create the triplestore
|
||||||
if err := logging.LogOperation(func() error {
|
if err := logging.LogOperation(func() error {
|
||||||
if err := dis.Triplestore().Provision(instance.GraphDBRepository, instance.Domain(), instance.GraphDBUser, instance.GraphDBPassword); err != nil {
|
if err := dis.Triplestore().Provision(instance.GraphDBRepository, instance.Domain(), instance.GraphDBUsername, instance.GraphDBPassword); err != nil {
|
||||||
return errProvisionGeneric.WithMessageF(slug, err)
|
return errProvisionGeneric.WithMessageF(slug, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
16
cmd/purge.go
16
cmd/purge.go
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
|
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/core"
|
"github.com/FAU-CDI/wisski-distillery/internal/core"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
||||||
"github.com/tkw1536/goprogram/exit"
|
"github.com/tkw1536/goprogram/exit"
|
||||||
)
|
)
|
||||||
|
|
@ -56,10 +56,10 @@ func (p purge) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// load the instance (first via bookkeeping, then via defaults)
|
// load the instance (first via bookkeeping, then via defaults)
|
||||||
logging.LogMessage(context.IOStream, "Checking bookkeeping table")
|
logging.LogMessage(context.IOStream, "Checking bookkeeping table")
|
||||||
instance, err := dis.Instance(slug)
|
instance, err := dis.Instances().WissKI(slug)
|
||||||
if err == wisski.ErrInstanceNotFound {
|
if err == instances.ErrWissKINotFound {
|
||||||
context.Println("Not found in bookkeeping table, assuming defaults")
|
context.Println("Not found in bookkeeping table, assuming defaults")
|
||||||
instance, err = dis.NewInstance(slug)
|
instance, err = dis.Instances().Create(slug)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errPurgeNoDetails.WithMessageF(err)
|
return errPurgeNoDetails.WithMessageF(err)
|
||||||
|
|
@ -80,8 +80,8 @@ func (p purge) Run(context wisski_distillery.Context) error {
|
||||||
// remove the triplestore
|
// remove the triplestore
|
||||||
ts := dis.Triplestore()
|
ts := dis.Triplestore()
|
||||||
logging.LogOperation(func() error {
|
logging.LogOperation(func() error {
|
||||||
logging.LogMessage(context.IOStream, "Removing user %s", instance.GraphDBUser)
|
logging.LogMessage(context.IOStream, "Removing user %s", instance.GraphDBUsername)
|
||||||
if err := ts.PurgeUser(instance.GraphDBUser); err != nil {
|
if err := ts.PurgeUser(instance.GraphDBUsername); err != nil {
|
||||||
context.EPrintln(err)
|
context.EPrintln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,8 +97,8 @@ func (p purge) Run(context wisski_distillery.Context) error {
|
||||||
logging.LogOperation(func() error {
|
logging.LogOperation(func() error {
|
||||||
sql := dis.SQL()
|
sql := dis.SQL()
|
||||||
|
|
||||||
logging.LogMessage(context.IOStream, "Removing user %s", instance.SqlUser)
|
logging.LogMessage(context.IOStream, "Removing user %s", instance.SqlUsername)
|
||||||
if err := sql.PurgeUser(instance.SqlUser); err != nil {
|
if err := sql.PurgeUser(instance.SqlUsername); err != nil {
|
||||||
context.EPrintln(err)
|
context.EPrintln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ var errRebuildFailed = exit.Error{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rb rebuild) Run(context wisski_distillery.Context) error {
|
func (rb rebuild) Run(context wisski_distillery.Context) error {
|
||||||
instances, err := context.Environment.Instances(rb.Positionals.Slug...)
|
instances, err := context.Environment.Instances().Load(rb.Positionals.Slug...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,12 @@ func (r reserve) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// check that it doesn't already exist
|
// check that it doesn't already exist
|
||||||
logging.LogMessage(context.IOStream, "Reserving new WissKI instance %s", slug)
|
logging.LogMessage(context.IOStream, "Reserving new WissKI instance %s", slug)
|
||||||
if exists, err := dis.HasInstance(slug); err != nil || exists {
|
if exists, err := dis.Instances().Has(slug); err != nil || exists {
|
||||||
return errProvisionAlreadyExists.WithMessageF(slug)
|
return errProvisionAlreadyExists.WithMessageF(slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make it in-memory
|
// make it in-memory
|
||||||
instance, err := dis.NewInstance(slug)
|
instance, err := dis.Instances().Create(slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errProvisionGeneric.WithMessageF(slug, err)
|
return errProvisionGeneric.WithMessageF(slug, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ var errShell = exit.Error{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sh shell) Run(context wisski_distillery.Context) error {
|
func (sh shell) Run(context wisski_distillery.Context) error {
|
||||||
instance, err := context.Environment.Instance(sh.Positionals.Slug)
|
instance, err := context.Environment.Instances().WissKI(sh.Positionals.Slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ var errSnapshotFailed = exit.Error{
|
||||||
|
|
||||||
func (bi snapshot) Run(context wisski_distillery.Context) error {
|
func (bi snapshot) Run(context wisski_distillery.Context) error {
|
||||||
dis := context.Environment
|
dis := context.Environment
|
||||||
instance, err := dis.Instance(bi.Positionals.Slug)
|
instance, err := dis.Instances().WissKI(bi.Positionals.Slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ func (bi snapshot) Run(context wisski_distillery.Context) error {
|
||||||
|
|
||||||
// take a snapshot into the staging area!
|
// take a snapshot into the staging area!
|
||||||
logging.LogOperation(func() error {
|
logging.LogOperation(func() error {
|
||||||
sreport := instance.Snapshot(context.IOStream, wisski.SnapshotDescription{
|
sreport := dis.Snapshot(instance, context.IOStream, wisski.SnapshotDescription{
|
||||||
Dest: sPath,
|
Dest: sPath,
|
||||||
Keepalive: bi.Keepalive,
|
Keepalive: bi.Keepalive,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
|
||||||
logging.LogMessage(context.IOStream, "Ensuring distillery installation directories exist")
|
logging.LogMessage(context.IOStream, "Ensuring distillery installation directories exist")
|
||||||
for _, d := range []string{
|
for _, d := range []string{
|
||||||
dis.Config.DeployRoot,
|
dis.Config.DeployRoot,
|
||||||
dis.InstancesDir(),
|
dis.Instances().Path(),
|
||||||
dis.SnapshotsStagingPath(),
|
dis.SnapshotsStagingPath(),
|
||||||
dis.SnapshotsArchivePath(),
|
dis.SnapshotsArchivePath(),
|
||||||
} {
|
} {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ var errPrefixUpdateFailed = exit.Error{
|
||||||
func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
|
func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
|
||||||
dis := context.Environment
|
dis := context.Environment
|
||||||
|
|
||||||
instances, err := dis.AllInstances()
|
instances, err := dis.Instances().All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errPrefixUpdateFailed.WithMessageF(err)
|
return errPrefixUpdateFailed.WithMessageF(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ type Instance struct {
|
||||||
|
|
||||||
// SQL Database credentials for the system
|
// SQL Database credentials for the system
|
||||||
SqlDatabase string `gorm:"column:sql_database;not null"`
|
SqlDatabase string `gorm:"column:sql_database;not null"`
|
||||||
SqlUser string `gorm:"column:sql_user;not null"`
|
SqlUsername string `gorm:"column:sql_user;not null"`
|
||||||
SqlPassword string `gorm:"column:sql_password;not null"`
|
SqlPassword string `gorm:"column:sql_password;not null"`
|
||||||
|
|
||||||
// GraphDB Repository
|
// GraphDB Repository
|
||||||
GraphDBRepository string `gorm:"column:graphdb_repository;not null"`
|
GraphDBRepository string `gorm:"column:graphdb_repository;not null"`
|
||||||
GraphDBUser string `gorm:"column:graphdb_user;not null"`
|
GraphDBUsername string `gorm:"column:graphdb_user;not null"`
|
||||||
GraphDBPassword string `gorm:"column:graphdb_password;not null"`
|
GraphDBPassword string `gorm:"column:graphdb_password;not null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,29 +26,28 @@ type Component interface {
|
||||||
// By convention it is /var/www/deploy/internal/core/${Name()}
|
// By convention it is /var/www/deploy/internal/core/${Name()}
|
||||||
Path() string
|
Path() string
|
||||||
|
|
||||||
// Context returns a new InstallationContext to be used during installation from the command line.
|
|
||||||
// Typically this should just pass through the parent, but might perform other tasks.
|
|
||||||
Context(parent InstallationContext) InstallationContext
|
|
||||||
|
|
||||||
// Base() returns a reference to a base component
|
// Base() returns a reference to a base component
|
||||||
// This is implemented by an embedding on ComponentBase
|
// This is implemented by an embedding on ComponentBase
|
||||||
Base() *ComponentBase
|
Base() *ComponentBase
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComponentWithStack implements a component with a Stack method.
|
// InstallableComponent implements an installable component
|
||||||
type ComponentWithStack interface {
|
type InstallableComponent interface {
|
||||||
Component
|
Component
|
||||||
|
|
||||||
// Stack can be used to gain access to the "docker compose" stack.
|
// Stack can be used to gain access to the "docker compose" stack.
|
||||||
//
|
//
|
||||||
// This should internally call
|
// This should internally call
|
||||||
Stack() Installable
|
Stack() Installable
|
||||||
|
|
||||||
|
// Context returns a new InstallationContext to be used during installation from the command line.
|
||||||
|
// Typically this should just pass through the parent, but might perform other tasks.
|
||||||
|
Context(parent InstallationContext) InstallationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComponentBase implements base functionality for a component
|
// ComponentBase implements base functionality for a component
|
||||||
type ComponentBase struct {
|
type ComponentBase struct {
|
||||||
Dir string // Dir is the directory this component lives in
|
Dir string // Dir is the directory this component lives in
|
||||||
|
|
||||||
Config *config.Config // Config is the configuration of the underlying distillery
|
Config *config.Config // Config is the configuration of the underlying distillery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
143
internal/component/instances/instances.go
Normal file
143
internal/component/instances/instances.go
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
package instances
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component/triplestore"
|
||||||
|
"github.com/tkw1536/goprogram/exit"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Instances manages multiple WissKI Instances.
|
||||||
|
type Instances struct {
|
||||||
|
component.ComponentBase
|
||||||
|
|
||||||
|
TS *triplestore.Triplestore
|
||||||
|
SQL *sql.SQL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Instances) Name() string {
|
||||||
|
return "instances"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrWissKINotFound is returned when a WissKI is not found
|
||||||
|
var ErrWissKINotFound = errors.New("WissKI not found")
|
||||||
|
|
||||||
|
var errSQL = exit.Error{
|
||||||
|
Message: "Unknown SQL Error %s",
|
||||||
|
ExitCode: exit.ExitGeneric,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
sql := instances.SQL
|
||||||
|
if err := sql.Wait(); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := sql.OpenBookkeeping(false)
|
||||||
|
if err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the instance by slug
|
||||||
|
query := table.Where(&bookkeeping.Instance{Slug: slug}).Find(&i.Instance)
|
||||||
|
switch {
|
||||||
|
case query.Error != nil:
|
||||||
|
return i, errSQL.WithMessageF(query.Error)
|
||||||
|
case query.RowsAffected == 0:
|
||||||
|
return i, ErrWissKINotFound
|
||||||
|
default:
|
||||||
|
i.instances = instances
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has checks if a WissKI with the provided slug exists inside the database.
|
||||||
|
// It does not perform any checks on the WissKI itself.
|
||||||
|
func (instances *Instances) Has(slug string) (ok bool, err error) {
|
||||||
|
sql := instances.SQL
|
||||||
|
if err := sql.Wait(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := sql.OpenBookkeeping(false)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := table.Select("count(*) > 0").Where("slug = ?", slug).Find(&ok)
|
||||||
|
if query.Error != nil {
|
||||||
|
return false, errSQL.WithMessageF(query.Error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return instances.find(true, func(table *gorm.DB) *gorm.DB {
|
||||||
|
return table
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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) {
|
||||||
|
if len(slugs) == 0 {
|
||||||
|
return instances.All()
|
||||||
|
}
|
||||||
|
return instances.WissKIs(slugs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
sql := instances.SQL
|
||||||
|
if err := sql.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the bookkeeping table
|
||||||
|
table, err := sql.OpenBookkeeping(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare a query
|
||||||
|
find := table
|
||||||
|
if order {
|
||||||
|
find = find.Order(clause.OrderByColumn{Column: clause.Column{Name: "slug"}, Desc: false})
|
||||||
|
}
|
||||||
|
if query != nil {
|
||||||
|
find = query(find)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch bookkeeping instances
|
||||||
|
var bks []bookkeeping.Instance
|
||||||
|
find = find.Find(&bks)
|
||||||
|
if find.Error != nil {
|
||||||
|
return nil, errSQL.WithMessageF(find.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make proper instances
|
||||||
|
results = make([]WissKI, len(bks))
|
||||||
|
for i, bk := range bks {
|
||||||
|
results[i].Instance = bk
|
||||||
|
results[i].instances = instances
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
61
internal/component/instances/wisski_create.go
Normal file
61
internal/component/instances/wisski_create.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package instances
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"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, err error) {
|
||||||
|
|
||||||
|
// make sure that the slug is valid!
|
||||||
|
if _, err := stringparser.ParseSlug(slug); err != nil {
|
||||||
|
return wisski, errInvalidSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
wisski.Instance.Slug = slug
|
||||||
|
wisski.Instance.FilesystemBase = filepath.Join(instances.Dir, slug)
|
||||||
|
|
||||||
|
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!
|
||||||
|
wisski.instances = instances
|
||||||
|
return wisski, nil
|
||||||
|
}
|
||||||
285
internal/component/instances/wisski_db.go
Normal file
285
internal/component/instances/wisski_db.go
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
package instances
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
|
||||||
|
"github.com/alessio/shellescape"
|
||||||
|
"github.com/tkw1536/goprogram/stream"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WissKI represents a single WissKI Instance
|
||||||
|
type WissKI struct {
|
||||||
|
// Whatever is stored inside the bookkeeping database
|
||||||
|
bookkeeping.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.OpenBookkeeping(false)
|
||||||
|
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.OpenBookkeeping(false)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shell executes a shell command inside the
|
||||||
|
func (wisski WissKI) Shell(io stream.IOStream, argv ...string) (int, error) {
|
||||||
|
return wisski.Stack().Exec(io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns the full domain name of this instance
|
||||||
|
func (wisski WissKI) Domain() string {
|
||||||
|
return fmt.Sprintf("%s.%s", wisski.Slug, wisski.instances.Config.DefaultDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL returns the public URL of this instance
|
||||||
|
func (wisski WissKI) URL() *url.URL {
|
||||||
|
// setup domain and path
|
||||||
|
url := &url.URL{
|
||||||
|
Host: wisski.Domain(),
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
// use http or https scheme depending on if the distillery has it enabled
|
||||||
|
if wisski.instances.Config.HTTPSEnabled() {
|
||||||
|
url.Scheme = "https"
|
||||||
|
} else {
|
||||||
|
url.Scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed all:instances/barrel instances/barrel.env
|
||||||
|
var barrelResources embed.FS
|
||||||
|
|
||||||
|
// Stack represents a stack representing this instance
|
||||||
|
func (wisski WissKI) Stack() component.Installable {
|
||||||
|
return component.Installable{
|
||||||
|
Stack: component.Stack{
|
||||||
|
Dir: wisski.FilesystemBase,
|
||||||
|
},
|
||||||
|
|
||||||
|
Resources: barrelResources,
|
||||||
|
ContextPath: filepath.Join("instances", "barrel"),
|
||||||
|
EnvPath: filepath.Join("instances", "barrel.env"),
|
||||||
|
|
||||||
|
EnvContext: map[string]string{
|
||||||
|
"DATA_PATH": filepath.Join(wisski.FilesystemBase, "data"),
|
||||||
|
|
||||||
|
"SLUG": wisski.Slug,
|
||||||
|
"VIRTUAL_HOST": wisski.Domain(),
|
||||||
|
|
||||||
|
"LETSENCRYPT_HOST": wisski.instances.Config.IfHttps(wisski.Domain()),
|
||||||
|
"LETSENCRYPT_EMAIL": wisski.instances.Config.IfHttps(wisski.instances.Config.CertbotEmail),
|
||||||
|
|
||||||
|
"RUNTIME_DIR": wisski.instances.Config.RuntimeDir(),
|
||||||
|
"GLOBAL_AUTHORIZED_KEYS_FILE": wisski.instances.Config.GlobalAuthorizedKeysFile,
|
||||||
|
},
|
||||||
|
|
||||||
|
MakeDirsPerm: fs.ModeDir | fs.ModePerm,
|
||||||
|
MakeDirs: []string{"data", ".composer"},
|
||||||
|
|
||||||
|
TouchFiles: []string{
|
||||||
|
filepath.Join("data", "authorized_keys"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed all:instances/reserve instances/reserve.env
|
||||||
|
var reserveResources embed.FS
|
||||||
|
|
||||||
|
func (wisski WissKI) ReserveStack() component.Installable {
|
||||||
|
return component.Installable{
|
||||||
|
Stack: component.Stack{
|
||||||
|
Dir: wisski.FilesystemBase,
|
||||||
|
},
|
||||||
|
|
||||||
|
Resources: reserveResources,
|
||||||
|
ContextPath: filepath.Join("instances", "reserve"),
|
||||||
|
EnvPath: filepath.Join("instances", "reserve.env"),
|
||||||
|
|
||||||
|
EnvContext: map[string]string{
|
||||||
|
"VIRTUAL_HOST": wisski.Domain(),
|
||||||
|
|
||||||
|
"LETSENCRYPT_HOST": wisski.instances.Config.IfHttps(wisski.Domain()),
|
||||||
|
"LETSENCRYPT_EMAIL": wisski.instances.Config.IfHttps(wisski.instances.Config.CertbotEmail),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision provisions an instance, assuming that the required databases already exist.
|
||||||
|
func (wisski WissKI) Provision(io stream.IOStream) error {
|
||||||
|
|
||||||
|
// create the basic st!
|
||||||
|
st := wisski.Stack()
|
||||||
|
if err := st.Install(io, component.InstallationContext{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull and build the stack!
|
||||||
|
if err := st.Update(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 := st.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoPrefix checks if this WissKI instance is excluded from generating prefixes
|
||||||
|
func (wisski *WissKI) NoPrefix() bool {
|
||||||
|
return fsx.IsFile(filepath.Join(wisski.FilesystemBase, "prefixes.skip"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var errPrefixExecFailed = errors.New("PrefixConfig: Failed to call list_uri_prefixes")
|
||||||
|
|
||||||
|
// PrefixConfig returns the prefix config belonging to this instance.
|
||||||
|
func (wisski *WissKI) PrefixConfig() (config string, err error) {
|
||||||
|
// if the user requested to skip the prefix, then don't do anything with it!
|
||||||
|
if wisski.NoPrefix() {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
// domain
|
||||||
|
builder.WriteString(wisski.URL().String() + ":")
|
||||||
|
builder.WriteString("\n")
|
||||||
|
|
||||||
|
// default prefixes
|
||||||
|
wu := stream.NewIOStream(&builder, nil, nil, 0)
|
||||||
|
code, err := wisski.Stack().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/list_uri_prefixes.php")
|
||||||
|
if err != nil || code != 0 {
|
||||||
|
return "", errPrefixExecFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom prefixes
|
||||||
|
prefixPath := filepath.Join(wisski.FilesystemBase, "prefixes")
|
||||||
|
if fsx.IsFile(prefixPath) {
|
||||||
|
prefix, err := os.Open(prefixPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer prefix.Close()
|
||||||
|
if _, err := io.Copy(&builder, prefix); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// and done!
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errPathbuildersExecFailed = errors.New("ExportPathbuilders: Failed to call export_pathbuilder")
|
||||||
|
|
||||||
|
// ExportPathbuilders writes pathbuilders into the directory dest
|
||||||
|
func (wisski *WissKI) ExportPathbuilders(dest string) error {
|
||||||
|
// export all the pathbuilders into the buffer
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
wu := stream.NewIOStream(&buffer, nil, nil, 0)
|
||||||
|
code, err := wisski.Stack().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/export_pathbuilder.php")
|
||||||
|
if err != nil || code != 0 {
|
||||||
|
return errPathbuildersExecFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode them as a json array
|
||||||
|
var pathbuilders map[string]string
|
||||||
|
if err := json.NewDecoder(&buffer).Decode(&pathbuilders); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort the names of the pathbuilders
|
||||||
|
names := maps.Keys(pathbuilders)
|
||||||
|
slices.Sort(names)
|
||||||
|
|
||||||
|
// write each into a file!
|
||||||
|
for _, name := range names {
|
||||||
|
pbxml := []byte(pathbuilders[name])
|
||||||
|
name := filepath.Join(dest, fmt.Sprintf("%s.xml", name))
|
||||||
|
if err := os.WriteFile(name, pbxml, fs.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/bookkeeping"
|
"github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/sqle"
|
"github.com/FAU-CDI/wisski-distillery/pkg/sqle"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/wait"
|
"github.com/FAU-CDI/wisski-distillery/pkg/wait"
|
||||||
|
|
@ -165,7 +165,7 @@ func (sql SQL) PurgeDatabase(db string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var errSQLUnableToCreateUser = errors.New("unable to create administrative user")
|
var errSQLUnableToCreateUser = errors.New("unable to create administrative user")
|
||||||
var errSQLUnsafeDatabaseName = errors.New("Bookkeeping database has an unsafe name")
|
var errSQLUnsafeDatabaseName = errors.New("bookkeeping database has an unsafe name")
|
||||||
var errSQLUnableToCreate = errors.New("unable to create bookkeeping database")
|
var errSQLUnableToCreate = errors.New("unable to create bookkeeping database")
|
||||||
|
|
||||||
// Bootstrap bootstraps the SQL database, and makes sure that the bookkeeping table is up-to-date
|
// Bootstrap bootstraps the SQL database, and makes sure that the bookkeeping table is up-to-date
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ func (backup *Backup) run(io stream.IOStream, dis *Distillery) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// list all instances
|
// list all instances
|
||||||
instances, err := dis.AllInstances()
|
instances, err := dis.Instances().All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
backup.InstanceListErr = err
|
backup.InstanceListErr = err
|
||||||
return
|
return
|
||||||
|
|
@ -224,7 +224,7 @@ func (backup *Backup) run(io stream.IOStream, dis *Distillery) {
|
||||||
}
|
}
|
||||||
|
|
||||||
files <- dir
|
files <- dir
|
||||||
return instance.Snapshot(iochild, SnapshotDescription{
|
return dis.Snapshot(instance, iochild, SnapshotDescription{
|
||||||
Dest: dir,
|
Dest: dir,
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/component/dis"
|
"github.com/FAU-CDI/wisski-distillery/internal/component/dis"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/component/resolver"
|
"github.com/FAU-CDI/wisski-distillery/internal/component/resolver"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/component/self"
|
"github.com/FAU-CDI/wisski-distillery/internal/component/self"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
||||||
|
|
@ -23,7 +24,7 @@ type components struct {
|
||||||
// m protects the fields below
|
// m protects the fields below
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
|
|
||||||
// each component is only initialized once
|
// installable components
|
||||||
web *web.Web
|
web *web.Web
|
||||||
self *self.Self
|
self *self.Self
|
||||||
resolver *resolver.Resolver
|
resolver *resolver.Resolver
|
||||||
|
|
@ -31,6 +32,9 @@ type components struct {
|
||||||
ssh *ssh.SSH
|
ssh *ssh.SSH
|
||||||
ts *triplestore.Triplestore
|
ts *triplestore.Triplestore
|
||||||
sql *sql.SQL
|
sql *sql.SQL
|
||||||
|
|
||||||
|
// other components
|
||||||
|
instances *instances.Instances
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeComponent makes or returns a component inside the [component] struct of the distillery
|
// makeComponent makes or returns a component inside the [component] struct of the distillery
|
||||||
|
|
@ -73,8 +77,8 @@ func makeComponent[C component.Component](dis *Distillery, field *C, init func(C
|
||||||
}
|
}
|
||||||
|
|
||||||
// Components returns all components that have a stack function
|
// Components returns all components that have a stack function
|
||||||
func (dis *Distillery) Components() []component.ComponentWithStack {
|
func (dis *Distillery) Components() []component.InstallableComponent {
|
||||||
return []component.ComponentWithStack{
|
return []component.InstallableComponent{
|
||||||
dis.Web(),
|
dis.Web(),
|
||||||
dis.Self(),
|
dis.Self(),
|
||||||
dis.Resolver(),
|
dis.Resolver(),
|
||||||
|
|
@ -122,3 +126,10 @@ func (dis *Distillery) Triplestore() *triplestore.Triplestore {
|
||||||
ts.PollInterval = time.Second
|
ts.PollInterval = time.Second
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dis *Distillery) Instances() *instances.Instances {
|
||||||
|
return makeComponent(dis, &dis.components.instances, func(instances *instances.Instances) {
|
||||||
|
instances.SQL = dis.SQL()
|
||||||
|
instances.TS = dis.Triplestore()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,408 +0,0 @@
|
||||||
package wisski
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"embed"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/bookkeeping"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
|
|
||||||
"github.com/alessio/shellescape"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/tkw1536/goprogram/exit"
|
|
||||||
"github.com/tkw1536/goprogram/stream"
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errNoBookkeeping = exit.Error{
|
|
||||||
Message: "instance %q does not exist in bookkeeping table",
|
|
||||||
ExitCode: exit.ExitGeneric,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrInstanceNotFound = exit.Error{
|
|
||||||
Message: "instance not found",
|
|
||||||
ExitCode: exit.ExitGeneric,
|
|
||||||
}
|
|
||||||
|
|
||||||
var errSQL = exit.Error{
|
|
||||||
Message: "Unknown SQL Error %s",
|
|
||||||
ExitCode: exit.ExitGeneric,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance returns the instance of the WissKI Distillery with the provided slug
|
|
||||||
func (dis *Distillery) Instance(slug string) (i Instance, err error) {
|
|
||||||
sql := dis.SQL()
|
|
||||||
if err := sql.Wait(); err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
table, err := sql.OpenBookkeeping(false)
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the instance by slug
|
|
||||||
query := table.Where(&bookkeeping.Instance{Slug: slug}).Find(&i.Instance)
|
|
||||||
switch {
|
|
||||||
case query.Error != nil:
|
|
||||||
return i, errSQL.WithMessageF(query.Error)
|
|
||||||
case query.RowsAffected == 0:
|
|
||||||
return i, ErrInstanceNotFound
|
|
||||||
default:
|
|
||||||
i.dis = dis
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasInstance checks if the provided instance exists in the bookeeping table
|
|
||||||
func (dis *Distillery) HasInstance(slug string) (ok bool, err error) {
|
|
||||||
sql := dis.SQL()
|
|
||||||
if err := sql.Wait(); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
table, err := sql.OpenBookkeeping(false)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
query := table.Select("count(*) > 0").Where("slug = ?", slug).Find(&ok)
|
|
||||||
if query.Error != nil {
|
|
||||||
return false, errSQL.WithMessageF(query.Error)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instances is like InstancesWith, except that when no slugs are provided, it calls AllInstances.
|
|
||||||
func (dis *Distillery) Instances(slugs ...string) ([]Instance, error) {
|
|
||||||
if len(slugs) == 0 {
|
|
||||||
return dis.AllInstances()
|
|
||||||
}
|
|
||||||
return dis.InstancesWith(slugs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllInstances 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 (dis *Distillery) AllInstances() ([]Instance, error) {
|
|
||||||
return dis.findInstances(true, func(table *gorm.DB) *gorm.DB {
|
|
||||||
return table
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstancesWith returns all instances where the slug is in the provided list of names.
|
|
||||||
// The returned instances are reordered in a consistent order.
|
|
||||||
func (dis *Distillery) InstancesWith(slugs ...string) ([]Instance, error) {
|
|
||||||
return dis.findInstances(true, func(table *gorm.DB) *gorm.DB {
|
|
||||||
return table.Where("slug IN ?", slugs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// findInstances finds instance objects based on a query in the bookkeeping table
|
|
||||||
func (dis *Distillery) findInstances(order bool, query func(table *gorm.DB) *gorm.DB) (instances []Instance, err error) {
|
|
||||||
sql := dis.SQL()
|
|
||||||
if err := sql.Wait(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// open the bookkeeping table
|
|
||||||
table, err := sql.OpenBookkeeping(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare a query
|
|
||||||
find := table
|
|
||||||
if order {
|
|
||||||
find = find.Order(clause.OrderByColumn{Column: clause.Column{Name: "slug"}, Desc: false})
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
find = query(find)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch bookkeeping instances
|
|
||||||
var bks []bookkeeping.Instance
|
|
||||||
find = find.Find(&bks)
|
|
||||||
if find.Error != nil {
|
|
||||||
return nil, errSQL.WithMessageF(find.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make proper instances
|
|
||||||
instances = make([]Instance, len(bks))
|
|
||||||
for i, bk := range bks {
|
|
||||||
instances[i].Instance = bk
|
|
||||||
instances[i].dis = dis
|
|
||||||
}
|
|
||||||
|
|
||||||
return instances, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance represents a bookkeeping instance
|
|
||||||
type Instance struct {
|
|
||||||
bookkeeping.Instance
|
|
||||||
|
|
||||||
// Credentials for the drupal instance
|
|
||||||
DrupalUsername string
|
|
||||||
DrupalPassword string
|
|
||||||
|
|
||||||
dis *Distillery
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates the bookkeeping table with this instance.
|
|
||||||
func (instance *Instance) Update() error {
|
|
||||||
db, err := instance.dis.SQL().OpenBookkeeping(false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// it has never been created => we need to create it in the database
|
|
||||||
if instance.Instance.Created.IsZero() {
|
|
||||||
return db.Create(&instance.Instance).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update based on the primary key!
|
|
||||||
return db.Where("pk = ?", instance.Instance.Pk).Updates(&instance.Instance).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes this instance from the bookkeeping table
|
|
||||||
func (instance *Instance) Delete() error {
|
|
||||||
db, err := instance.dis.SQL().OpenBookkeeping(false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// doesn't exist => nothing to delete
|
|
||||||
if instance.Instance.Created.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete it directly
|
|
||||||
return db.Delete(&instance.Instance).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell executes a shell command inside the
|
|
||||||
func (instance Instance) Shell(io stream.IOStream, argv ...string) (int, error) {
|
|
||||||
return instance.Stack().Exec(io, "barrel", "/bin/sh", append([]string{"/user_shell.sh"}, argv...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain returns the full domain name of this instance
|
|
||||||
func (instance Instance) Domain() string {
|
|
||||||
return fmt.Sprintf("%s.%s", instance.Slug, instance.dis.Config.DefaultDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL returns the public URL of this instance
|
|
||||||
func (instance Instance) URL() *url.URL {
|
|
||||||
// setup domain and path
|
|
||||||
url := &url.URL{
|
|
||||||
Host: instance.Domain(),
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
|
|
||||||
// use http or https scheme depending on if the distillery has it enabled
|
|
||||||
if instance.dis.Config.HTTPSEnabled() {
|
|
||||||
url.Scheme = "https"
|
|
||||||
} else {
|
|
||||||
url.Scheme = "http"
|
|
||||||
}
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed all:instances/barrel instances/barrel.env
|
|
||||||
var barrelResources embed.FS
|
|
||||||
|
|
||||||
// Stack represents a stack representing this instance
|
|
||||||
func (instance Instance) Stack() component.Installable {
|
|
||||||
return component.Installable{
|
|
||||||
Stack: component.Stack{
|
|
||||||
Dir: instance.FilesystemBase,
|
|
||||||
},
|
|
||||||
|
|
||||||
Resources: barrelResources,
|
|
||||||
ContextPath: filepath.Join("instances", "barrel"),
|
|
||||||
EnvPath: filepath.Join("instances", "barrel.env"),
|
|
||||||
|
|
||||||
EnvContext: map[string]string{
|
|
||||||
"DATA_PATH": filepath.Join(instance.FilesystemBase, "data"),
|
|
||||||
|
|
||||||
"SLUG": instance.Slug,
|
|
||||||
"VIRTUAL_HOST": instance.Domain(),
|
|
||||||
|
|
||||||
"LETSENCRYPT_HOST": instance.dis.Config.IfHttps(instance.Domain()),
|
|
||||||
"LETSENCRYPT_EMAIL": instance.dis.Config.IfHttps(instance.dis.Config.CertbotEmail),
|
|
||||||
|
|
||||||
"RUNTIME_DIR": instance.dis.Config.RuntimeDir(),
|
|
||||||
"GLOBAL_AUTHORIZED_KEYS_FILE": instance.dis.Config.GlobalAuthorizedKeysFile,
|
|
||||||
},
|
|
||||||
|
|
||||||
MakeDirsPerm: fs.ModeDir | fs.ModePerm,
|
|
||||||
MakeDirs: []string{"data", ".composer"},
|
|
||||||
|
|
||||||
TouchFiles: []string{
|
|
||||||
filepath.Join("data", "authorized_keys"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed all:instances/reserve instances/reserve.env
|
|
||||||
var reserveResources embed.FS
|
|
||||||
|
|
||||||
func (instance Instance) ReserveStack() component.Installable {
|
|
||||||
return component.Installable{
|
|
||||||
Stack: component.Stack{
|
|
||||||
Dir: instance.FilesystemBase,
|
|
||||||
},
|
|
||||||
|
|
||||||
Resources: reserveResources,
|
|
||||||
ContextPath: filepath.Join("instances", "reserve"),
|
|
||||||
EnvPath: filepath.Join("instances", "reserve.env"),
|
|
||||||
|
|
||||||
EnvContext: map[string]string{
|
|
||||||
"VIRTUAL_HOST": instance.Domain(),
|
|
||||||
|
|
||||||
"LETSENCRYPT_HOST": instance.dis.Config.IfHttps(instance.Domain()),
|
|
||||||
"LETSENCRYPT_EMAIL": instance.dis.Config.IfHttps(instance.dis.Config.CertbotEmail),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provision provisions an instance, assuming that the required databases already exist.
|
|
||||||
func (instance Instance) Provision(io stream.IOStream) error {
|
|
||||||
|
|
||||||
// create the basic st!
|
|
||||||
st := instance.Stack()
|
|
||||||
if err := st.Install(io, component.InstallationContext{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull and build the stack!
|
|
||||||
if err := st.Update(io, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionParams := []string{
|
|
||||||
instance.Domain(),
|
|
||||||
|
|
||||||
instance.SqlDatabase,
|
|
||||||
instance.SqlUser,
|
|
||||||
instance.SqlPassword,
|
|
||||||
|
|
||||||
instance.GraphDBRepository,
|
|
||||||
instance.GraphDBUser,
|
|
||||||
instance.GraphDBPassword,
|
|
||||||
|
|
||||||
instance.DrupalUsername,
|
|
||||||
instance.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 := st.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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) NoPrefix() bool {
|
|
||||||
return fsx.IsFile(filepath.Join(instance.FilesystemBase, "prefixes.skip"))
|
|
||||||
}
|
|
||||||
|
|
||||||
var errPrefixExecFailed = errors.New("PrefixConfig: Failed to call list_uri_prefixes")
|
|
||||||
|
|
||||||
// PrefixConfig returns the prefix config belonging to this instance.
|
|
||||||
func (instance *Instance) PrefixConfig() (config string, err error) {
|
|
||||||
// if the user requested to skip the prefix, then don't do anything with it!
|
|
||||||
if instance.NoPrefix() {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder strings.Builder
|
|
||||||
|
|
||||||
// domain
|
|
||||||
builder.WriteString(instance.URL().String() + ":")
|
|
||||||
builder.WriteString("\n")
|
|
||||||
|
|
||||||
// default prefixes
|
|
||||||
wu := stream.NewIOStream(&builder, nil, nil, 0)
|
|
||||||
code, err := instance.Stack().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/list_uri_prefixes.php")
|
|
||||||
if err != nil || code != 0 {
|
|
||||||
return "", errPrefixExecFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom prefixes
|
|
||||||
prefixPath := filepath.Join(instance.FilesystemBase, "prefixes")
|
|
||||||
if fsx.IsFile(prefixPath) {
|
|
||||||
prefix, err := os.Open(prefixPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer prefix.Close()
|
|
||||||
if _, err := io.Copy(&builder, prefix); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
builder.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// and done!
|
|
||||||
return builder.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errPathbuildersExecFailed = errors.New("ExportPathbuilders: Failed to call export_pathbuilder")
|
|
||||||
|
|
||||||
// ExportPathbuilders writes pathbuilders into the directory dest
|
|
||||||
func (instance *Instance) ExportPathbuilders(dest string) error {
|
|
||||||
// export all the pathbuilders into the buffer
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
wu := stream.NewIOStream(&buffer, nil, nil, 0)
|
|
||||||
code, err := instance.Stack().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/export_pathbuilder.php")
|
|
||||||
if err != nil || code != 0 {
|
|
||||||
return errPathbuildersExecFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode them as a json array
|
|
||||||
var pathbuilders map[string]string
|
|
||||||
if err := json.NewDecoder(&buffer).Decode(&pathbuilders); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort the names of the pathbuilders
|
|
||||||
names := maps.Keys(pathbuilders)
|
|
||||||
slices.Sort(names)
|
|
||||||
|
|
||||||
// write each into a file!
|
|
||||||
for _, name := range names {
|
|
||||||
pbxml := []byte(pathbuilders[name])
|
|
||||||
name := filepath.Join(dest, fmt.Sprintf("%s.xml", name))
|
|
||||||
if err := os.WriteFile(name, pbxml, fs.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
package wisski
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/bookkeeping"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/stringparser"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (dis *Distillery) InstancesDir() string {
|
|
||||||
return filepath.Join(dis.Config.DeployRoot, "instances")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dis *Distillery) InstanceDir(slug string) string {
|
|
||||||
return filepath.Join(dis.InstancesDir(), slug)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dis *Distillery) InstanceSQL(slug string) (database, user string) {
|
|
||||||
database = dis.Config.MysqlDatabasePrefix + slug
|
|
||||||
user = dis.Config.MysqlUserPrefix + slug
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dis *Distillery) InstanceGraphDB(slug string) (repo, user string) {
|
|
||||||
repo = dis.Config.GraphDBRepoPrefix + slug
|
|
||||||
user = dis.Config.GraphDBUserPrefix + slug
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidSlug = errors.New("Not a valid slug")
|
|
||||||
|
|
||||||
// NewInstance fills the struct for a new distillery 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 (dis *Distillery) NewInstance(slug string) (i Instance, err error) {
|
|
||||||
|
|
||||||
// make sure that the slug is valid!
|
|
||||||
if _, err := stringparser.ParseSlug(slug); err != nil {
|
|
||||||
return i, errInvalidSlug
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate sql data
|
|
||||||
sqlPassword, err := dis.Config.NewPassword()
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
sqlDB, sqlUser := dis.InstanceSQL(slug)
|
|
||||||
|
|
||||||
// generate ts data
|
|
||||||
tsPassword, err := dis.Config.NewPassword()
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
tsRepo, tsUser := dis.InstanceGraphDB(slug)
|
|
||||||
|
|
||||||
// generate drupal data
|
|
||||||
drPassword, err := dis.Config.NewPassword()
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
drUser := "admin"
|
|
||||||
|
|
||||||
// make the instance object!
|
|
||||||
instance := bookkeeping.Instance{
|
|
||||||
Slug: slug,
|
|
||||||
|
|
||||||
OwnerEmail: "",
|
|
||||||
AutoBlindUpdateEnabled: true,
|
|
||||||
|
|
||||||
FilesystemBase: dis.InstanceDir(slug),
|
|
||||||
|
|
||||||
SqlDatabase: sqlDB,
|
|
||||||
SqlUser: sqlUser,
|
|
||||||
SqlPassword: sqlPassword,
|
|
||||||
|
|
||||||
GraphDBRepository: tsRepo,
|
|
||||||
GraphDBUser: tsUser,
|
|
||||||
GraphDBPassword: tsPassword,
|
|
||||||
}
|
|
||||||
|
|
||||||
i.DrupalUsername = drUser
|
|
||||||
i.DrupalPassword = drPassword
|
|
||||||
|
|
||||||
// store the instance in the object and return it!
|
|
||||||
i.Instance = instance
|
|
||||||
i.dis = dis
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
@ -19,7 +19,7 @@ func (dis *Distillery) Server() *Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
instances, err := s.dis.AllInstances()
|
instances, err := s.dis.Instances().All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
io.WriteString(w, "Something went wrong")
|
io.WriteString(w, "Something went wrong")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/bookkeeping"
|
"github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/countwriter"
|
"github.com/FAU-CDI/wisski-distillery/pkg/countwriter"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
||||||
|
|
@ -161,7 +162,7 @@ func (snapshot Snapshot) Report(w io.Writer) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot creates a new snapshot of this instance into dest
|
// Snapshot creates a new snapshot of this instance into dest
|
||||||
func (instance Instance) Snapshot(io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) {
|
func (dis *Distillery) Snapshot(instance instances.WissKI, io stream.IOStream, desc SnapshotDescription) (snapshot Snapshot) {
|
||||||
// setup the snapshot
|
// setup the snapshot
|
||||||
snapshot.Description = desc
|
snapshot.Description = desc
|
||||||
snapshot.Instance = instance.Instance
|
snapshot.Instance = instance.Instance
|
||||||
|
|
@ -175,8 +176,8 @@ func (instance Instance) Snapshot(io stream.IOStream, desc SnapshotDescription)
|
||||||
logging.LogOperation(func() error {
|
logging.LogOperation(func() error {
|
||||||
snapshot.StartTime = time.Now()
|
snapshot.StartTime = time.Now()
|
||||||
|
|
||||||
snapshot.makeBlackbox(io, instance)
|
snapshot.makeBlackbox(io, dis, instance)
|
||||||
snapshot.makeWhitebox(io, instance)
|
snapshot.makeWhitebox(io, dis, instance)
|
||||||
|
|
||||||
snapshot.EndTime = time.Now()
|
snapshot.EndTime = time.Now()
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -188,7 +189,7 @@ func (instance Instance) Snapshot(io stream.IOStream, desc SnapshotDescription)
|
||||||
|
|
||||||
// makeBlackbox runs the blackbox backup of the system.
|
// makeBlackbox runs the blackbox backup of the system.
|
||||||
// It pauses the Instance, if a consistent state is required.
|
// It pauses the Instance, if a consistent state is required.
|
||||||
func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, instance Instance) {
|
func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, dis *Distillery, instance instances.WissKI) {
|
||||||
stack := instance.Stack()
|
stack := instance.Stack()
|
||||||
|
|
||||||
og := opgroup.NewOpGroup[string](4)
|
og := opgroup.NewOpGroup[string](4)
|
||||||
|
|
@ -243,7 +244,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, instance Instance) {
|
||||||
defer nquads.Close()
|
defer nquads.Close()
|
||||||
|
|
||||||
// directly store the result
|
// directly store the result
|
||||||
_, err = instance.dis.Triplestore().Backup(nquads, instance.GraphDBRepository)
|
_, err = dis.Triplestore().Backup(nquads, instance.GraphDBRepository)
|
||||||
return err
|
return err
|
||||||
}, &snapshot.ErrTriplestore)
|
}, &snapshot.ErrTriplestore)
|
||||||
|
|
||||||
|
|
@ -259,7 +260,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, instance Instance) {
|
||||||
defer sql.Close()
|
defer sql.Close()
|
||||||
|
|
||||||
// directly store the result
|
// directly store the result
|
||||||
return instance.dis.SQL().Backup(io, sql, instance.SqlDatabase)
|
return dis.SQL().Backup(io, sql, instance.SqlDatabase)
|
||||||
}, &snapshot.ErrSQL)
|
}, &snapshot.ErrSQL)
|
||||||
|
|
||||||
// wait for the group!
|
// wait for the group!
|
||||||
|
|
@ -268,7 +269,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, instance Instance) {
|
||||||
|
|
||||||
// makeWhitebox runs the whitebox backup of the system.
|
// makeWhitebox runs the whitebox backup of the system.
|
||||||
// The instance should be running during this step.
|
// The instance should be running during this step.
|
||||||
func (snapshot *Snapshot) makeWhitebox(io stream.IOStream, instance Instance) {
|
func (snapshot *Snapshot) makeWhitebox(io stream.IOStream, dis *Distillery, instance instances.WissKI) {
|
||||||
og := opgroup.NewOpGroup[string](1)
|
og := opgroup.NewOpGroup[string](1)
|
||||||
|
|
||||||
// write pathbuilders
|
// write pathbuilders
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue