Refactor components

This commit is contained in:
Tom Wiesing 2022-09-12 14:44:45 +02:00
parent 17011a277f
commit 487ce09979
No known key found for this signature in database
7 changed files with 117 additions and 63 deletions

View file

@ -34,6 +34,10 @@ type Component interface {
// 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
// This is implemented by an embedding on ComponentBase
Base() *ComponentBase
}
// ComponentBase implements base functionality for a component
@ -43,6 +47,11 @@ type ComponentBase struct {
Config *config.Config // Config is the configuration of the underlying distillery
}
// Base returns a reference to the ComponentBase
func (cb *ComponentBase) Base() *ComponentBase {
return cb
}
// Path returns the path to this component
func (cb ComponentBase) Path() string {
return cb.Dir

134
env/component.go vendored
View file

@ -2,6 +2,8 @@ package env
import (
"path/filepath"
"reflect"
"sync"
"time"
"github.com/FAU-CDI/wisski-distillery/component"
@ -14,9 +16,63 @@ import (
"github.com/FAU-CDI/wisski-distillery/component/web"
)
// Stacks returns the Stacks of this distillery
// components holds the various components of the distillery
// It is inlined into the [Distillery] struct, and initialized using [makeComponent].
type components struct {
// m protects the fields below
m sync.Mutex
// each component is only initialized once
web *web.Web
self *self.Self
resolver *resolver.Resolver
dis *dis.Dis
ssh *ssh.SSH
ts *triplestore.Triplestore
sql *sql.SQL
}
// makeComponent makes or returns a component inside the [component] struct of the distillery
//
// C is the type of component to initialize. It must be backed by a pointer, or makeComponent will panic.
//
// dis is the distillery to initialize components for
// field is a pointer to the appropriate struct field within the distillery components
// init is called with a new non-nil component to initialize it. It may be nil, to indicate no initialization is required.
//
// makeComponent returns the new or existing component instance
func makeComponent[C component.Component](dis *Distillery, field *C, init func(C)) C {
dis.components.m.Lock()
defer dis.components.m.Unlock()
// get the typeof C and make sure that it is a pointer type!
typC := reflect.TypeOf((*C)(nil)).Elem()
if typC.Kind() != reflect.Pointer {
panic("makeComponent: C must be backed by a pointer")
}
// if the component is non-nil, then it has already been initialized
if !reflect.ValueOf(*field).IsNil() {
return *field
}
// create a new element, and call the initializer (if requested)
*field = reflect.New(typC.Elem()).Interface().(C)
if init != nil {
init(*field)
}
// apply the base configuration
base := (*field).Base()
base.Config = dis.Config
base.Dir = filepath.Join(dis.Config.DeployRoot, "core", (*field).Name())
// and eventually return it
return *field
}
// Components returns all components of the distillery
func (dis *Distillery) Components() []component.Component {
// TODO: Do we want to cache these components?
return []component.Component{
dis.Web(),
dis.Self(),
@ -28,63 +84,43 @@ func (dis *Distillery) Components() []component.Component {
}
}
// Web returns the web component belonging to this distillery
func (dis *Distillery) Web() (web web.Web) {
dis.makeComponent(web, &web.ComponentBase)
return
func (dis *Distillery) Web() *web.Web {
return makeComponent(dis, &dis.components.web, nil)
}
// Self returns the self component belonging to this distillery
func (dis *Distillery) Self() (self self.Self) {
dis.makeComponent(self, &self.ComponentBase)
return
func (dis *Distillery) Self() *self.Self {
return makeComponent(dis, &dis.components.self, nil)
}
// Resolver returns the resolver component belonging to this distillery
func (dis *Distillery) Resolver() (resolver resolver.Resolver) {
resolver.ConfigName = "prefix.cfg" // TODO: Move into core?
resolver.Executable = dis.CurrentExecutable()
dis.makeComponent(resolver, &resolver.ComponentBase)
return
func (dis *Distillery) Resolver() *resolver.Resolver {
return makeComponent(dis, &dis.components.resolver, func(resolver *resolver.Resolver) {
resolver.ConfigName = "prefix.cfg" // TODO: Move into core?
resolver.Executable = dis.CurrentExecutable()
})
}
// Dis returns the dis component belonging to this distillery
func (dis *Distillery) Dis() (ddis dis.Dis) {
ddis.Executable = dis.CurrentExecutable()
dis.makeComponent(ddis, &ddis.ComponentBase)
return
func (d *Distillery) Dis() *dis.Dis {
return makeComponent(d, &d.components.dis, func(ddis *dis.Dis) {
ddis.Executable = d.CurrentExecutable()
})
}
// SSH returns the SSH component belonging to this distillery
func (dis *Distillery) SSH() (ssh ssh.SSH) {
dis.makeComponent(ssh, &ssh.ComponentBase)
return
func (dis *Distillery) SSH() *ssh.SSH {
return makeComponent(dis, &dis.components.ssh, nil)
}
// SQL returns the SQL component belonging to this distillery
func (dis *Distillery) SQL() (sql sql.SQL) {
sql.ServerURL = dis.Upstream.SQL
sql.PollContext = dis.Context()
sql.PollInterval = time.Second
dis.makeComponent(sql, &sql.ComponentBase)
return
func (dis *Distillery) SQL() *sql.SQL {
return makeComponent(dis, &dis.components.sql, func(sql *sql.SQL) {
sql.ServerURL = dis.Upstream.SQL
sql.PollContext = dis.Context()
sql.PollInterval = time.Second
})
}
// Triplestore returns the TriplestoreComponent belonging to this distillery
func (dis *Distillery) Triplestore() (ts triplestore.Triplestore) {
ts.BaseURL = "http://" + dis.Upstream.Triplestore
ts.PollContext = dis.Context()
ts.PollInterval = time.Second
dis.makeComponent(ts, &ts.ComponentBase)
return
}
// makeComponent updates the baseComponent belonging to component
func (dis *Distillery) makeComponent(component component.Component, base *component.ComponentBase) {
base.Config = dis.Config
base.Dir = filepath.Join(dis.Config.DeployRoot, "core", component.Name())
func (dis *Distillery) Triplestore() *triplestore.Triplestore {
return makeComponent(dis, &dis.components.ts, func(ts *triplestore.Triplestore) {
ts.BaseURL = "http://" + dis.Upstream.Triplestore
ts.PollContext = dis.Context()
ts.PollInterval = time.Second
})
}

19
env/distillery.go vendored
View file

@ -10,10 +10,21 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/fsx"
)
// Distillery represents a running instance for the distillery
// Distillery represents an interface to the running distillery.
type Distillery struct {
Config *config.Config
Upstream Upstream // TODO: not sure this belongs here
// Config holds the configuration of the distillery.
// It is read directly from a configuration file.
Config *config.Config
// Upstream holds information to connect to the various running
// distillery components.
//
// NOTE(twiesing): This is intended to eventually allow full remote management of the distillery.
// But for now this will just hold upstream configuration.
Upstream Upstream
// components hold references to the various components of the distillery.
components
}
// Upstream are the upstream urls connecting to the various external components.
@ -23,7 +34,7 @@ type Upstream struct {
}
// Context returns a new Context belonging to this distillery
func (dis Distillery) Context() context.Context {
func (dis *Distillery) Context() context.Context {
return context.Background()
}

2
env/runtime.go vendored
View file

@ -3,6 +3,6 @@ package env
import "path/filepath"
// RuntimeDir returns the path to the runtime directory
func (dis Distillery) RuntimeDir() string {
func (dis *Distillery) RuntimeDir() string {
return filepath.Join(dis.Config.DeployRoot, "runtime")
}

12
env/snapshot.go vendored
View file

@ -16,25 +16,25 @@ import (
)
// SnapshotsDir returns the path that contains all snapshot related data.
func (dis Distillery) SnapshotsDir() string {
func (dis *Distillery) SnapshotsDir() string {
return filepath.Join(dis.Config.DeployRoot, "snapshots")
}
// SnapshotsStagingPath returns the path to the directory containing a temporary staging area for snapshots.
// Use NewSnapshotStagingDir to generate a new staging area.
func (dis Distillery) SnapshotsStagingPath() string {
func (dis *Distillery) SnapshotsStagingPath() string {
return filepath.Join(dis.SnapshotsDir(), "staging")
}
// SnapshotsArchivePath returns the path to the directory containing all exported archives.
// Use NewSnapshotArchivePath to generate a path to a new archive in this directory.
func (dis Distillery) SnapshotsArchivePath() string {
func (dis *Distillery) SnapshotsArchivePath() string {
return filepath.Join(dis.SnapshotsDir(), "archives")
}
// NewSnapshotArchivePath returns the path to a new archive with the provided prefix.
// The path is guaranteed to not exist.
func (dis Distillery) NewSnapshotArchivePath(prefix string) (path string) {
func (dis *Distillery) NewSnapshotArchivePath(prefix string) (path string) {
// TODO: Consider moving these into a subdirectory with the provided prefix.
for path == "" || fsx.Exists(path) {
name := dis.newSnapshotName(prefix) + ".tar.gz"
@ -45,7 +45,7 @@ func (dis Distillery) NewSnapshotArchivePath(prefix string) (path string) {
// newSnapshot name returns a new basename for a snapshot with the provided prefix.
// The name is guaranteed to be unique within this process.
func (Distillery) newSnapshotName(prefix string) string {
func (*Distillery) newSnapshotName(prefix string) string {
suffix, _ := password.Password(64) // silently ignore any errors!
if prefix == "" {
prefix = "backup"
@ -57,7 +57,7 @@ func (Distillery) newSnapshotName(prefix string) string {
// NewSnapshotStagingDir returns the path to a new snapshot directory.
// The directory is guaranteed to have been freshly created.
func (dis Distillery) NewSnapshotStagingDir(prefix string) (path string, err error) {
func (dis *Distillery) NewSnapshotStagingDir(prefix string) (path string, err error) {
for path == "" || os.IsExist(err) {
path = filepath.Join(dis.SnapshotsStagingPath(), dis.newSnapshotName(prefix))
err = os.Mkdir(path, os.ModeDir)

View file

@ -48,8 +48,6 @@ var ErrCopyNoDirectory = errors.New("dst is not a directory")
//
// onCopy, when not nil, is called for each file or directory being copied.
func CopyDirectory(dst, src string, onCopy func(dst, src string)) error {
// TODO: Allow copying in parallel? Maybe with a mutex?
// sanity checks
if SameFile(src, dst) {
return ErrCopySameFile

View file

@ -1,4 +1,4 @@
// Package hostname provides hostname
// Package hostname provides the hostname.
package hostname
import (