diff --git a/cmd/backup.go b/cmd/backup.go index 1faf386..ee4d7ca 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -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", diff --git a/cmd/blind_update.go b/cmd/blind_update.go index 3006d67..06dd01c 100644 --- a/cmd/blind_update.go +++ b/cmd/blind_update.go @@ -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", diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 007096e..005360f 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -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() diff --git a/cmd/config.go b/cmd/config.go index 455cfef..e9ced84 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -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", diff --git a/cmd/cron.go b/cmd/cron.go index 17f1e97..9747d9a 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -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", diff --git a/cmd/dis_server.go b/cmd/dis_server.go new file mode 100644 index 0000000..e43ecec --- /dev/null +++ b/cmd/dis_server.go @@ -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) +} diff --git a/cmd/info.go b/cmd/info.go index 0b99540..150ca54 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -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", diff --git a/cmd/license.go b/cmd/license.go index ba95d46..a29b324 100644 --- a/cmd/license.go +++ b/cmd/license.go @@ -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", diff --git a/cmd/ls.go b/cmd/ls.go index bb336f5..bcb4ed4 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -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", diff --git a/cmd/make_mysql_account.go b/cmd/make_mysql_account.go index 8a0a2f9..30ef6d2 100644 --- a/cmd/make_mysql_account.go +++ b/cmd/make_mysql_account.go @@ -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{ diff --git a/cmd/monday.go b/cmd/monday.go index 9449985..02cc9ed 100644 --- a/cmd/monday.go +++ b/cmd/monday.go @@ -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", diff --git a/cmd/mysql.go b/cmd/mysql.go index 7e19a1b..ace11c8 100644 --- a/cmd/mysql.go +++ b/cmd/mysql.go @@ -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{ diff --git a/cmd/provision.go b/cmd/provision.go index b3a0535..01ab371 100644 --- a/cmd/provision.go +++ b/cmd/provision.go @@ -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", diff --git a/cmd/purge.go b/cmd/purge.go index f08d3c6..7811cd0 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -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", diff --git a/cmd/rebuild.go b/cmd/rebuild.go index b852a1e..f1ed46b 100644 --- a/cmd/rebuild.go +++ b/cmd/rebuild.go @@ -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", diff --git a/cmd/reserve.go b/cmd/reserve.go index 0098a68..f729ba0 100644 --- a/cmd/reserve.go +++ b/cmd/reserve.go @@ -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", diff --git a/cmd/shell.go b/cmd/shell.go index f4c1a3c..f22af7a 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -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{ diff --git a/cmd/snapshot.go b/cmd/snapshot.go index 0d11361..70acaeb 100644 --- a/cmd/snapshot.go +++ b/cmd/snapshot.go @@ -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", diff --git a/cmd/system_update.go b/cmd/system_update.go index e622d79..1262a8f 100644 --- a/cmd/system_update.go +++ b/cmd/system_update.go @@ -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 { diff --git a/cmd/update_prefix_config.go b/cmd/update_prefix_config.go index 471070b..6d5ee83 100644 --- a/cmd/update_prefix_config.go +++ b/cmd/update_prefix_config.go @@ -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", diff --git a/cmd/wdcli/main.go b/cmd/wdcli/main.go index 78de9dc..cfc56ca 100644 --- a/cmd/wdcli/main.go +++ b/cmd/wdcli/main.go @@ -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) } diff --git a/core/core.go b/core/core.go new file mode 100644 index 0000000..b58fd66 --- /dev/null +++ b/core/core.go @@ -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" diff --git a/core/flags.go b/core/flags.go new file mode 100644 index 0000000..0271659 --- /dev/null +++ b/core/flags.go @@ -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"` +} diff --git a/core/meta.go b/core/meta.go new file mode 100644 index 0000000..2d446ff --- /dev/null +++ b/core/meta.go @@ -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) +} diff --git a/core/params.go b/core/params.go new file mode 100644 index 0000000..008a0b9 --- /dev/null +++ b/core/params.go @@ -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 +} diff --git a/env/requirements.go b/core/requirements.go similarity index 78% rename from env/requirements.go rename to core/requirements.go index 66e6fed..9f2fae9 100644 --- a/env/requirements.go +++ b/core/requirements.go @@ -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 } diff --git a/distillery/resources/compose/dis/Dockerfile b/distillery/resources/compose/dis/Dockerfile new file mode 100644 index 0000000..7e87236 --- /dev/null +++ b/distillery/resources/compose/dis/Dockerfile @@ -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"] \ No newline at end of file diff --git a/distillery/resources/compose/dis/docker-compose.yml b/distillery/resources/compose/dis/docker-compose.yml new file mode 100644 index 0000000..ebec445 --- /dev/null +++ b/distillery/resources/compose/dis/docker-compose.yml @@ -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 diff --git a/distillery/resources/templates/docker-env/dis b/distillery/resources/templates/docker-env/dis new file mode 100644 index 0000000..939a420 --- /dev/null +++ b/distillery/resources/templates/docker-env/dis @@ -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} \ No newline at end of file diff --git a/env/component.go b/env/component.go index 5594e8c..5d39f2a 100644 --- a/env/component.go +++ b/env/component.go @@ -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 } diff --git a/env/component_dis.go b/env/component_dis.go new file mode 100644 index 0000000..32e971d --- /dev/null +++ b/env/component_dis.go @@ -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 +} diff --git a/env/component_resolver.go b/env/component_resolver.go index 374a846..4097e1b 100644 --- a/env/component_resolver.go +++ b/env/component_resolver.go @@ -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 } diff --git a/env/component_self.go b/env/component_self.go index 8cd69da..88f7c47 100644 --- a/env/component_self.go +++ b/env/component_self.go @@ -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 { diff --git a/env/component_sql.go b/env/component_sql.go index d2fbe1a..a9ae523 100644 --- a/env/component_sql.go +++ b/env/component_sql.go @@ -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, } diff --git a/env/component_ssh.go b/env/component_ssh.go index bf0d5a8..08d90bb 100644 --- a/env/component_ssh.go +++ b/env/component_ssh.go @@ -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 } diff --git a/env/component_triplestore.go b/env/component_triplestore.go index 2680dcf..efe7a73 100644 --- a/env/component_triplestore.go +++ b/env/component_triplestore.go @@ -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{ diff --git a/env/component_web.go b/env/component_web.go index 0156349..ba269b5 100644 --- a/env/component_web.go +++ b/env/component_web.go @@ -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 } diff --git a/env/constants.go b/env/constants.go index 86f8064..dfe6a41 100644 --- a/env/constants.go +++ b/env/constants.go @@ -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 } diff --git a/env/distillery.go b/env/distillery.go index 6e59deb..75d8050 100644 --- a/env/distillery.go +++ b/env/distillery.go @@ -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 } diff --git a/env/params.go b/env/params.go deleted file mode 100644 index 9c14222..0000000 --- a/env/params.go +++ /dev/null @@ -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, - ) -} diff --git a/env/server.go b/env/server.go new file mode 100644 index 0000000..b7498a5 --- /dev/null +++ b/env/server.go @@ -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") + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 5caff52..1995a94 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 == "" { diff --git a/program.go b/program.go index 3ac19e9..87b048e 100644 --- a/program.go +++ b/program.go @@ -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) }, } }