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

View file

@ -5,6 +5,7 @@ import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/FAU-CDI/wisski-distillery/internal/targz"
@ -24,7 +25,7 @@ type backup struct {
func (backup) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "backup",

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/execx"
"github.com/tkw1536/goprogram/exit"
)
@ -19,7 +19,7 @@ type blindUpdate struct {
func (blindUpdate) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "blind_update",

View file

@ -6,8 +6,8 @@ import (
"path/filepath"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/distillery"
"github.com/FAU-CDI/wisski-distillery/env"
cfg "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/fsx"
"github.com/FAU-CDI/wisski-distillery/internal/hostname"
@ -26,7 +26,7 @@ type bootstrap struct {
func (bootstrap) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: false,
},
Command: "bootstrap",
@ -74,7 +74,7 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
// check that we didn't get a different base directory
{
got, err := env.ReadBaseDirectory()
got, err := core.ReadBaseDirectory()
if err == nil && got != "" && got != root {
return errBootstrapDifferent.WithMessageF(got)
}
@ -85,15 +85,15 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
if err := os.MkdirAll(root, fs.ModeDir); err != nil {
return errBootstrapFailedToCreateDirectory.WithMessageF(root)
}
if err := env.WriteBaseDirectory(root); err != nil {
if err := core.WriteBaseDirectory(root); err != nil {
return errBootstrapFailedToSaveDirectory.WithMessageF(root)
}
context.Println(root)
}
// TODO: Read these from the command line?
wdcliPath := filepath.Join(root, env.Executable)
envPath := filepath.Join(root, env.ConfigFile)
// TODO: Should we read an existing configuration file?
wdcliPath := filepath.Join(root, core.Executable)
envPath := filepath.Join(root, core.ConfigFile)
domain := bs.Hostname
if domain == "" {
domain = hostname.FQDN()

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
)
// Config is the configuration command
@ -13,7 +13,7 @@ type config struct {
func (s config) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "config",

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/tkw1536/goprogram/exit"
)
@ -18,7 +18,7 @@ type cron struct {
func (cron) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "cron",

43
cmd/dis_server.go Normal file
View file

@ -0,0 +1,43 @@
package cmd
import (
"net/http"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/tkw1536/goprogram/exit"
)
// DisServer is the 'dis_server' command
var DisServer wisski_distillery.Command = disServer{}
type disServer struct {
Prefix string `short:"p" long:"prefix" description:"prefix to listen under"`
Bind string `short:"b" long:"bind" description:"address to listen on" default:"127.0.0.1:8888"`
}
func (disServer) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "dis_server",
Description: "Starts a server with information about this distillery",
}
}
var errServerListen = exit.Error{
ExitCode: exit.ExitGeneric,
Message: "Unable to listen",
}
func (s disServer) Run(context wisski_distillery.Context) error {
server := context.Environment.Server()
context.Printf("Listening on %s\n", s.Bind)
err := http.ListenAndServe(s.Bind, http.StripPrefix(s.Prefix, server))
if err == nil {
return nil
}
return errServerListen.Wrap(err)
}

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
)
// Info is then 'info' command
@ -16,7 +16,7 @@ type info struct {
func (info) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "info",

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/legal"
)
@ -15,7 +15,7 @@ type license struct{}
func (license) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: false,
},
Command: "license",

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
)
// Ls is the 'ls' command
@ -16,7 +16,7 @@ type ls struct {
func (ls) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "ls",

View file

@ -4,7 +4,7 @@ import (
"fmt"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/sqle"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/parser"
@ -17,7 +17,7 @@ type makeMysqlAccount struct{}
func (makeMysqlAccount) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
ParserConfig: parser.Config{

View file

@ -4,7 +4,7 @@ import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
)
@ -20,7 +20,7 @@ type monday struct {
func (monday) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "monday",

View file

@ -4,7 +4,7 @@ import (
"fmt"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/parser"
)
@ -20,7 +20,7 @@ type mysql struct {
func (mysql) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
ParserConfig: parser.Config{

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/fsx"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/tkw1536/goprogram/exit"
@ -19,7 +19,7 @@ type provision struct {
func (provision) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "provision",

View file

@ -4,6 +4,7 @@ import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/tkw1536/goprogram/exit"
@ -21,7 +22,7 @@ type purge struct {
func (purge) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "purge",

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/FAU-CDI/wisski-distillery/internal/stack"
"github.com/tkw1536/goprogram/exit"
@ -19,7 +19,7 @@ type rebuild struct {
func (rebuild) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "rebuild",

View file

@ -2,7 +2,7 @@ package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/fsx"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/FAU-CDI/wisski-distillery/internal/stack"
@ -20,7 +20,7 @@ type reserve struct {
func (reserve) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "reserve",

View file

@ -4,7 +4,7 @@ import (
"fmt"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/parser"
)
@ -21,7 +21,7 @@ type shell struct {
func (shell) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
ParserConfig: parser.Config{

View file

@ -5,6 +5,7 @@ import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/FAU-CDI/wisski-distillery/internal/targz"
@ -26,7 +27,7 @@ type snapshot struct {
func (snapshot) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "snapshot",

View file

@ -5,8 +5,8 @@ import (
"path/filepath"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/internal/execx"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/FAU-CDI/wisski-distillery/internal/stack"
@ -26,7 +26,7 @@ type systemupdate struct {
func (systemupdate) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
ParserConfig: parser.Config{
@ -124,6 +124,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
if err := logging.LogOperation(func() error {
for _, component := range dis.Components() {
stack := component.Stack()
ctx := component.Context(ctx)
if err := logging.LogOperation(func() error {
return stack.Install(context.IOStream, ctx)
}, context.IOStream, "Installing docker stack %q", component.Name()); err != nil {

View file

@ -5,7 +5,7 @@ import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/internal/logging"
"github.com/tkw1536/goprogram/exit"
)
@ -17,7 +17,7 @@ type updateprefixconfig struct{}
func (updateprefixconfig) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: env.Requirements{
Requirements: core.Requirements{
NeedsDistillery: true,
},
Command: "update_prefix_config",

View file

@ -8,7 +8,7 @@ import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/cmd"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/goprogram/stream"
)
@ -48,6 +48,9 @@ func init() {
wdcli.Register(cmd.Backup)
wdcli.Register(cmd.Cron)
wdcli.Register(cmd.Monday)
// servers
wdcli.Register(cmd.DisServer)
}
// an error when no arguments are provided.
@ -83,7 +86,7 @@ func main() {
// creat a new set of parameters
// and then use them to execute the main command
err := func() error {
params, err := env.ParamsFromEnv()
params, err := core.ParamsFromEnv()
if err != nil {
return streams.Die(err)
}

14
core/core.go Normal file
View file

@ -0,0 +1,14 @@
// Package core implements the core of the WissKI Distillery and the wdcli executable.
// It does not depend on any other packages.
package core
// BaseDirectoryDefault is the default deploy directory to load the distillery from.
const BaseDirectoryDefault = "/var/www/deploy"
// Executable is the name of the 'wdcli' executable.
// It should be located inside the deployment directory.
const Executable = "wdcli"
// Config file is the name of the config file.
// It should be located inside the deployment directory.
const ConfigFile = ".env"

8
core/flags.go Normal file
View file

@ -0,0 +1,8 @@
package core
// Flags are global flags for the wdcli executable
type Flags struct {
ConfigPath string `short:"c" long:"config" description:"Path to distillery configuration file"`
InternalInDocker bool `long:"internal-in-docker" description:"Internal Flag to signal the shell that it is running inside a docker stack belonging to the distillery"`
}

71
core/meta.go Normal file
View file

@ -0,0 +1,71 @@
package core
import (
"errors"
"io/fs"
"os"
"os/user"
"path/filepath"
"strings"
)
// MetaConfigFile is the path to a configuration file that contains the path to the last used wdcli executable.
// It is expected to be in the current user's home directory.
//
// You probably want to use [MetaConfigPath] instead.
//
// It should contain the path to a deployment directory.
const MetaConfigFile = "." + Executable
// MetaConfigPath returns the full path to the MetaConfigPath()
func MetaConfigPath() (string, error) {
// find the current user
usr, err := user.Current()
if err != nil {
return "", err
}
return filepath.Join(usr.HomeDir, MetaConfigFile), nil
}
var errReadBaseDirectoryEmpty = errors.New("ReadBaseDirectory: Directory is empty")
// ReadBaseDirectory reads the base deployment directory from the environment.
// Use [ParamsFromEnv] to initialize parameters completely.
//
// It does not perform any reading of files.
func ReadBaseDirectory() (value string, err error) {
// get the path!
path, err := MetaConfigPath()
if err != nil {
return "", err
}
// read the meta config file!
contents, err := os.ReadFile(path)
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 "", errReadBaseDirectoryEmpty
}
// and return it!
return value, nil
}
// WriteBaseDirectory writes the base directory to the environment, or returns an error
func WriteBaseDirectory(dir string) error {
// get the path!
path, err := MetaConfigPath()
if err != nil {
return err
}
// just put the directory inside it!
return os.WriteFile(path, []byte(dir), fs.ModePerm)
}

31
core/params.go Normal file
View file

@ -0,0 +1,31 @@
package core
import (
"os"
"path/filepath"
)
// Params are used to initialize the excutable.
type Params struct {
ConfigPath string // ConfigPath is the path to the configuration file for the distillery
}
// ParamsFromEnv creates a new set of parameters from the environment.
// Uses [ReadBaseDirectory] or falls back to [BaseDirectoryDefault].
func ParamsFromEnv() (params Params, err error) {
// try to read the base directory!
value, err := ReadBaseDirectory()
switch {
case os.IsNotExist(err):
params.ConfigPath = BaseDirectoryDefault
case err == nil:
params.ConfigPath = value
default:
return params, err
}
// and add the configuration file name to it!
params.ConfigPath = filepath.Join(params.ConfigPath, ConfigFile)
return params, nil
}

View file

@ -1,10 +1,11 @@
package env
package core
import (
"github.com/tkw1536/goprogram"
"github.com/tkw1536/goprogram/meta"
)
// Requirements are requirements for the WissKI Distillery
type Requirements struct {
// Do we need an installed distillery?
NeedsDistillery bool
@ -13,13 +14,13 @@ type Requirements struct {
// 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
return r.NeedsDistillery
}
// 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 {
func (r Requirements) Validate(arguments goprogram.Arguments[Flags]) error {
return nil
}

View file

@ -0,0 +1,5 @@
FROM docker.io/library/alpine
COPY wdcli /wdcli
EXPOSE 8888
CMD ["/wdcli","--internal-in-docker","--config","${CONFIG_PATH}","dis_server","--bind","0.0.0.0:8888"]

View file

@ -0,0 +1,28 @@
version: "3.7"
services:
wdresolve:
build: .
restart: always
environment:
# port and hostname for this image to use
VIRTUAL_HOST: ${VIRTUAL_HOST}
VIRTUAL_PORT: 8888
VIRTUAL_PATH: /dis/
CONFIG_PATH: ${CONFIG_PATH}
# optional letsencrypt email
LETSENCRYPT_HOST: ${LETSENCRYPT_HOST}
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
volumes:
- "${CONFIG_PATH}:${CONFIG_PATH}:ro"
- "${DEPLOY_ROOT}:${DEPLOY_ROOT}:ro"
- "${GLOBAL_AUTHORIZED_KEYS_FILE}:${GLOBAL_AUTHORIZED_KEYS_FILE}:ro"
- "${SELF_OVERRIDES_FILE}:${SELF_OVERRIDES_FILE}:ro"
networks:
default:
name: distillery
external: true

View file

@ -0,0 +1,9 @@
VIRTUAL_HOST=${VIRTUAL_HOST}
LETSENCRYPT_HOST=${LETSENCRYPT_HOST}
LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
CONFIG_PATH=${CONFIG_PATH}
DEPLOY_ROOT=${DEPLOY_ROOT}
GLOBAL_AUTHORIZED_KEYS_FILE=${GLOBAL_AUTHORIZED_KEYS_FILE}
SELF_OVERRIDES_FILE=${SELF_OVERRIDES_FILE}

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,
)
}

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")
}
}

View file

@ -73,6 +73,9 @@ type Config struct {
// admin credentials for the Mysql database
MysqlAdminUser string `env:"MYSQL_ADMIN_USER" default:"admin" validator:"is_nonempty"`
MysqlAdminPassword string `env:"MYSQL_ADMIN_PASSWORD" default:"admin" validator:"is_nonempty"`
// ConfigPath is the path this configuration was loaded from (if any)
ConfigPath string
}
func (config Config) String() string {
@ -87,7 +90,12 @@ func (config Config) String() string {
tField := tConfig.Field(i)
vField := vConfig.FieldByName(tField.Name)
fmt.Fprintf(values, "%s=%v\n", tField.Tag.Get("env"), vField.Interface())
env := tField.Tag.Get("env")
if env == "" {
continue
}
fmt.Fprintf(values, "%s=%v\n", env, vField.Interface())
}
return values.String()
@ -113,6 +121,11 @@ func (config *Config) Unmarshal(src io.Reader) error {
dflt := tField.Tag.Get("default")
validator := tField.Tag.Get("validator")
// skip it if it isn't loaded!
if env == "" {
continue
}
// read the value with a default
value, ok := values[env]
if !ok || value == "" {

View file

@ -3,6 +3,7 @@ package wisski_distillery
import (
"os/user"
"github.com/FAU-CDI/wisski-distillery/core"
"github.com/FAU-CDI/wisski-distillery/env"
"github.com/tkw1536/goprogram"
"github.com/tkw1536/goprogram/exit"
@ -11,9 +12,9 @@ import (
// these define the ggman-specific program types
// none of these are strictly needed, they're just around for convenience
type wdcliEnv = *env.Distillery
type wdcliParameters = env.Params
type wdcliRequirements = env.Requirements
type wdCliFlags = struct{}
type wdcliParameters = core.Params
type wdcliRequirements = core.Requirements
type wdCliFlags = core.Flags
type Program = goprogram.Program[wdcliEnv, wdcliParameters, wdCliFlags, wdcliRequirements]
type Command = goprogram.Command[wdcliEnv, wdcliParameters, wdCliFlags, wdcliRequirements]
@ -42,7 +43,7 @@ func NewProgram() Program {
if context.Description.Requirements.NeedsDistillery {
dis := context.Environment
if !dis.UsingDistilleryExecutable() {
context.EPrintf(warnNoDeployWdcli, env.Executable, dis.ExecutablePath())
context.EPrintf(warnNoDeployWdcli, core.Executable, dis.ExecutablePath())
}
}
@ -50,7 +51,7 @@ func NewProgram() Program {
},
NewEnvironment: func(params wdcliParameters, context Context) (e wdcliEnv, err error) {
return env.NewDistillery(params, context.Description.Requirements)
return env.NewDistillery(params, context.Args.Flags, context.Description.Requirements)
},
}
}