Add 'dis' component

This commit adds a new 'dis' component to the distillery that serves a
list of all known instances for the moment.
This commit is contained in:
Tom Wiesing 2022-09-09 17:10:24 +02:00
parent 35bb95c5ca
commit 4b357476a3
No known key found for this signature in database
43 changed files with 434 additions and 167 deletions

6
env/component.go vendored
View file

@ -10,11 +10,12 @@ import (
// Stacks returns the Stacks of this distillery
func (dis *Distillery) Components() []Component {
// TODO: Do we want to cache these stacks?
// TODO: Do we want to cache these components?
return []Component{
dis.Web(),
dis.Self(),
dis.Resolver(),
dis.Dis(),
dis.SSH(),
dis.Triplestore(),
dis.SQL(),
@ -25,7 +26,8 @@ func (dis *Distillery) Components() []Component {
type Component interface {
Name() string // Name is the name of this component
Stack() stack.Installable // Stack returns the installable stack representing this component
Stack() stack.Installable // Stack returns the installable stack representing this component
Context(parent stack.InstallationContext) stack.InstallationContext // context for installation
Path() string // Path returns the path to this component
}

47
env/component_dis.go vendored Normal file
View file

@ -0,0 +1,47 @@
package env
import (
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/stack"
)
// DisComponent represents the 'dis' layer belonging to a distillery
type DisComponent struct {
dis *Distillery
}
// Dis returns the DisComponent belonging to this distillery
func (dis *Distillery) Dis() DisComponent {
return DisComponent{dis: dis}
}
func (DisComponent) Name() string {
return "dis"
}
func (dis DisComponent) Stack() stack.Installable {
return dis.dis.makeComponentStack(dis, stack.Installable{
EnvFileContext: map[string]string{
"VIRTUAL_HOST": dis.dis.DefaultVirtualHost(),
"LETSENCRYPT_HOST": dis.dis.DefaultLetsencryptHost(),
"LETSENCRYPT_EMAIL": dis.dis.Config.CertbotEmail,
"CONFIG_PATH": dis.dis.Config.ConfigPath,
"DEPLOY_ROOT": dis.dis.Config.DeployRoot,
"GLOBAL_AUTHORIZED_KEYS_FILE": dis.dis.Config.GlobalAuthorizedKeysFile,
"SELF_OVERRIDES_FILE": dis.dis.Config.SelfOverridesFile,
},
CopyContextFiles: []string{core.Executable},
})
}
func (dis DisComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return stack.InstallationContext{
core.Executable: dis.dis.CurrentExecutable(),
}
}
func (dis DisComponent) Path() string {
return dis.Stack().Dir
}

View file

@ -44,6 +44,10 @@ func (resolver ResolverComponent) Stack() stack.Installable {
return stack
}
func (ResolverComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return parent
}
func (resolver ResolverComponent) Path() string {
return resolver.Stack().Dir
}

View file

@ -16,6 +16,10 @@ func (SelfComponent) Name() string {
return "self"
}
func (SelfComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return parent
}
func (sc SelfComponent) Stack() stack.Installable {
TARGET := "https://github.com/FAU-CDI/wisski-distillery"
if sc.dis.Config.SelfRedirect != nil {

View file

@ -20,6 +20,8 @@ import (
// SQLComponent represents the 'sql' layer belonging to a distillery
type SQLComponent struct {
ServerURL string
PollInterval time.Duration // Duration to wait for during wait
dis *Distillery
@ -28,6 +30,7 @@ type SQLComponent struct {
// SSH returns the SSHComponent belonging to this distillery
func (dis *Distillery) SQL() SQLComponent {
return SQLComponent{
ServerURL: dis.Upstream.SQL,
PollInterval: time.Second,
dis: dis,
@ -38,6 +41,10 @@ func (SQLComponent) Name() string {
return "sql"
}
func (SQLComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return parent
}
// Stack returns the docker stack that handles the sql database.
func (sql SQLComponent) Stack() stack.Installable {
return sql.dis.makeComponentStack(sql, stack.Installable{
@ -56,7 +63,7 @@ func (sql SQLComponent) Path() string {
// sqlOpen opens a new sql connection to the provided database using the administrative credentials
func (sql SQLComponent) openDatabase(database string, config *gorm.Config) (*gorm.DB, error) {
cfg := mysql.Config{
DSN: fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", sql.dis.Config.MysqlAdminUser, sql.dis.Config.MysqlAdminPassword, "127.0.0.1:3306", database),
DSN: fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", sql.dis.Config.MysqlAdminUser, sql.dis.Config.MysqlAdminPassword, sql.ServerURL, database),
DefaultStringSize: 256,
}

View file

@ -20,6 +20,10 @@ func (ssh SSHComponent) Stack() stack.Installable {
return ssh.dis.makeComponentStack(ssh, stack.Installable{})
}
func (SSHComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return parent
}
func (ssh SSHComponent) Path() string {
return ssh.Stack().Dir
}

View file

@ -32,7 +32,7 @@ type TriplestoreComponent struct {
// Triplestore returns the TriplestoreComponent belonging to this distillery
func (dis *Distillery) Triplestore() TriplestoreComponent {
return TriplestoreComponent{
BaseURL: "http://127.0.0.1:7200",
BaseURL: "http://" + dis.Upstream.Triplestore,
PollInterval: time.Second,
dis: dis,
@ -43,6 +43,10 @@ func (TriplestoreComponent) Name() string {
return "triplestore"
}
func (TriplestoreComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return parent
}
// Stack returns the installable Triplestore stack
func (ts TriplestoreComponent) Stack() stack.Installable {
return ts.dis.makeComponentStack(ts, stack.Installable{

View file

@ -24,6 +24,10 @@ func (web WebComponent) Stack() stack.Installable {
})
}
func (WebComponent) Context(parent stack.InstallationContext) stack.InstallationContext {
return parent
}
func (web WebComponent) Path() string {
return web.Stack().Dir
}

23
env/constants.go vendored
View file

@ -4,16 +4,13 @@ import (
"os"
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/fsx"
)
// Executable is the name of the 'wdcli' executable.
// It should be located inside the deployment directory.
const Executable = "wdcli"
// ExecutablePath returns the path to the executable of this distillery.
func (dis *Distillery) ExecutablePath() string {
return filepath.Join(dis.Config.DeployRoot, Executable)
return filepath.Join(dis.Config.DeployRoot, core.Executable)
}
// UsingDistilleryExecutable checks if the current process
@ -25,12 +22,12 @@ func (dis *Distillery) UsingDistilleryExecutable() bool {
return fsx.SameFile(exe, dis.ExecutablePath())
}
// Config file is the name of the config file.
// It should be located inside the deployment directory.
const ConfigFile = ".env"
// ConfigFilePath returns the path to the configuration file of this distillery.
// TODO: This should be moved to the Config struct.
func (dis *Distillery) ConfigFilePath() string {
return filepath.Join(dis.Config.DeployRoot, ConfigFile)
// CurrentExecutable returns the path to the current executable being used.
// When it does not exist, falls back to the default executable.
func (dis *Distillery) CurrentExecutable() string {
exe, err := os.Executable()
if err != nil || !fsx.IsFile(exe) {
return dis.ExecutablePath()
}
return exe
}

38
env/distillery.go vendored
View file

@ -5,13 +5,21 @@ import (
"os"
"strings"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/tkw1536/goprogram/exit"
)
// Distillery represents a running instance for the distillery
type Distillery struct {
Config *config.Config
Config *config.Config
Upstream Upstream
}
// Upstream are the upstream urls connecting to the various external components.
type Upstream struct {
SQL string
Triplestore string
}
func (dis Distillery) HTTPSEnabled() bool {
@ -50,28 +58,44 @@ var errOpenConfig = exit.Error{
}
// NewDistillery creates a new distillery object from a set of parameters and requirements
func NewDistillery(params Params, req Requirements) (env *Distillery, err error) {
env = &Distillery{}
func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) (env *Distillery, err error) {
env = &Distillery{
Upstream: Upstream{
SQL: "127.0.0.1:3306",
Triplestore: "127.0.0.1:7200",
},
}
if flags.InternalInDocker {
env.Upstream.SQL = "sql:3306"
env.Upstream.Triplestore = "triplestore:7200"
}
// if we don't need to load the config, there is nothing to do
if !req.NeedsDistillery {
return
}
// if there is no no config file, return
cfg := params.ConfigFilePath()
// try to find the configuration file
cfg := flags.ConfigPath // command line flags first
if cfg == "" {
cfg = params.ConfigPath // then globally provided files
}
if cfg == "" {
return nil, errNoConfigFile
}
f, err := os.Open(params.ConfigFilePath())
// open the config file!
f, err := os.Open(params.ConfigPath)
if err != nil {
return nil, errOpenConfig.WithMessageF(err)
}
defer f.Close()
// unmarshal the config
env.Config = &config.Config{}
env.Config = &config.Config{
ConfigPath: cfg,
}
err = env.Config.Unmarshal(f)
return
}

92
env/params.go vendored
View file

@ -1,92 +0,0 @@
package env
import (
"errors"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/tkw1536/goprogram/exit"
)
// Params are parameters used for initialization of the environment
type Params struct {
BaseDirectory string
}
// ConfigFilePath returns the path to the configuration file
func (params Params) ConfigFilePath() string {
if params.BaseDirectory == "" {
return ""
}
return filepath.Join(params.BaseDirectory, ConfigFile)
}
var errUnableToLoadParams = exit.Error{
ExitCode: exit.ExitGeneralArguments,
Message: "Unable to configure wdcli environment: %s",
}
const BaseDirectoryDefault = "/var/www/deploy"
// ParamsFromEnv creates a new set of parameters from the environment.
// There is no guarantee that the parameters are valid.
func ParamsFromEnv() (params Params, err error) {
// try to read the base directory
value, err := ReadBaseDirectory()
switch {
case os.IsNotExist(err):
params.BaseDirectory = BaseDirectoryDefault
case err == nil:
params.BaseDirectory = value
default:
return params, errUnableToLoadParams.WithMessageF(err)
}
return params, nil
}
var baseConfigFile = ".wdcli"
// ReadBaseDirectory reads the base directory from the environment, or an empty string
func ReadBaseDirectory() (value string, err error) {
// find the current user
usr, err := user.Current()
if err != nil {
return "", err
}
// read the base config file!
contents, err := os.ReadFile(filepath.Join(usr.HomeDir, baseConfigFile))
if err != nil {
return "", err
}
// and trim the spaces!
value = strings.TrimSpace(string(contents))
// check that it is actually set!
if len(value) == 0 {
return "", errors.New("ReadBaseDirectory: Directory is empty")
}
// and return it!
return value, nil
}
// WriteBaseDirectory writes the base directory to the environment, or returns an error
func WriteBaseDirectory(dir string) error {
// find the current user
usr, err := user.Current()
if err != nil {
return err
}
// read the base config file!
return os.WriteFile(
filepath.Join(usr.HomeDir, baseConfigFile),
[]byte(dir),
os.ModePerm,
)
}

25
env/requirements.go vendored
View file

@ -1,25 +0,0 @@
package env
import (
"github.com/tkw1536/goprogram"
"github.com/tkw1536/goprogram/meta"
)
type Requirements struct {
// Do we need an installed distillery?
NeedsDistillery bool
}
// AllowsFlag checks if the provided flag may be passed to fullfill this requirement
// By default it is used only for help page generation, and may be inaccurate.
func (r Requirements) AllowsFlag(flag meta.Flag) bool {
return true
}
// Validate validates if this requirement is fullfilled for the provided global flags.
// It should return either nil, or an error of type exit.Error.
//
// Validate does not take into account AllowsOption, see ValidateAllowedOptions.
func (r Requirements) Validate(arguments goprogram.Arguments[struct{}]) error {
return nil
}

31
env/server.go vendored Normal file
View file

@ -0,0 +1,31 @@
package env
import (
"io"
"net/http"
)
// Server represents a server for this distillery
type Server struct {
dis *Distillery
}
func (dis *Distillery) Server() *Server {
return &Server{
dis: dis,
}
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
instances, err := s.dis.AllInstances()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, "Something went wrong")
return
}
w.WriteHeader(http.StatusOK)
for _, instance := range instances {
io.WriteString(w, instance.Slug+"\n")
}
}