From 487ce09979650effbdc6807e92607f89529ac328 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Mon, 12 Sep 2022 14:44:45 +0200 Subject: [PATCH] Refactor components --- component/component.go | 9 +++ env/component.go | 134 +++++++++++++++++++++------------- env/distillery.go | 19 ++++- env/runtime.go | 2 +- env/snapshot.go | 12 +-- internal/fsx/copy.go | 2 - internal/hostname/hostname.go | 2 +- 7 files changed, 117 insertions(+), 63 deletions(-) diff --git a/component/component.go b/component/component.go index 4cb26de..d8b60ed 100644 --- a/component/component.go +++ b/component/component.go @@ -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 diff --git a/env/component.go b/env/component.go index a8e4b7b..f834c9c 100644 --- a/env/component.go +++ b/env/component.go @@ -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 + }) } diff --git a/env/distillery.go b/env/distillery.go index 8fc902e..c7d039b 100644 --- a/env/distillery.go +++ b/env/distillery.go @@ -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() } diff --git a/env/runtime.go b/env/runtime.go index 4b784d8..fac70f2 100644 --- a/env/runtime.go +++ b/env/runtime.go @@ -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") } diff --git a/env/snapshot.go b/env/snapshot.go index 9db1e07..40c66d7 100644 --- a/env/snapshot.go +++ b/env/snapshot.go @@ -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) diff --git a/internal/fsx/copy.go b/internal/fsx/copy.go index 9d570b2..bad0d32 100644 --- a/internal/fsx/copy.go +++ b/internal/fsx/copy.go @@ -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 diff --git a/internal/hostname/hostname.go b/internal/hostname/hostname.go index a252dde..a3ecea8 100644 --- a/internal/hostname/hostname.go +++ b/internal/hostname/hostname.go @@ -1,4 +1,4 @@ -// Package hostname provides hostname +// Package hostname provides the hostname. package hostname import (