From 35bb95c5ca0458962d12b4b5fc02c54dd14d5e7c Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Fri, 9 Sep 2022 13:35:02 +0200 Subject: [PATCH] Show a warning when using wrong executable This commit updates the 'wdcli' command to show a warning when using the wrong executable. --- cmd/backup.go | 2 +- cmd/blind_update.go | 2 +- cmd/bootstrap.go | 6 +-- cmd/config.go | 2 +- cmd/cron.go | 2 +- cmd/info.go | 2 +- cmd/license.go | 2 +- cmd/ls.go | 2 +- cmd/make_mysql_account.go | 2 +- cmd/monday.go | 2 +- cmd/mysql.go | 2 +- cmd/provision.go | 2 +- cmd/purge.go | 2 +- cmd/rebuild.go | 2 +- cmd/reserve.go | 2 +- cmd/shell.go | 2 +- cmd/snapshot.go | 2 +- cmd/system_update.go | 2 +- cmd/update_prefix_config.go | 2 +- env/constants.go | 36 +++++++++++++++ env/distillery.go | 2 +- env/params.go | 2 +- env/requirements.go | 3 +- internal/fsx/copy.go | 4 +- internal/fsx/same.go | 87 +++++++++++++++++++++++++++++++++++ internal/fsx/touch.go | 6 ++- internal/fsx/type.go | 8 +++- internal/stack/installable.go | 2 +- program.go | 14 +++++- 29 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 env/constants.go create mode 100644 internal/fsx/same.go diff --git a/cmd/backup.go b/cmd/backup.go index a31f468..1faf386 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -25,7 +25,7 @@ type backup struct { func (backup) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "backup", Description: "Makes a backup of the entire distillery", diff --git a/cmd/blind_update.go b/cmd/blind_update.go index 18fc5fd..3006d67 100644 --- a/cmd/blind_update.go +++ b/cmd/blind_update.go @@ -20,7 +20,7 @@ type blindUpdate struct { func (blindUpdate) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "blind_update", Description: "Runs the blind update in the provided instances", diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index c6515c9..007096e 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -27,7 +27,7 @@ type bootstrap struct { func (bootstrap) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: false, + NeedsDistillery: false, }, Command: "bootstrap", Description: "Bootstraps the installation of a Distillery System", @@ -92,8 +92,8 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error { } // TODO: Read these from the command line? - wdcliPath := filepath.Join(root, "wdcli") - envPath := filepath.Join(root, ".env") + wdcliPath := filepath.Join(root, env.Executable) + envPath := filepath.Join(root, env.ConfigFile) domain := bs.Hostname if domain == "" { domain = hostname.FQDN() diff --git a/cmd/config.go b/cmd/config.go index e1b499b..455cfef 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -14,7 +14,7 @@ type config struct { func (s config) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "config", Description: "Prints information about configuration", diff --git a/cmd/cron.go b/cmd/cron.go index 4d142bb..17f1e97 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -19,7 +19,7 @@ type cron struct { func (cron) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "cron", Description: "Runs the cron script for several instances", diff --git a/cmd/info.go b/cmd/info.go index 89de7dd..0b99540 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -17,7 +17,7 @@ type info struct { func (info) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "info", Description: "Provide information about a single repository", diff --git a/cmd/license.go b/cmd/license.go index 7195680..ba95d46 100644 --- a/cmd/license.go +++ b/cmd/license.go @@ -16,7 +16,7 @@ type license struct{} func (license) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: false, + NeedsDistillery: false, }, Command: "license", Description: "Print licensing information about wdcli and exit. ", diff --git a/cmd/ls.go b/cmd/ls.go index ddc56cd..bb336f5 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -17,7 +17,7 @@ type ls struct { func (ls) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "ls", Description: "Lists WissKI instances", diff --git a/cmd/make_mysql_account.go b/cmd/make_mysql_account.go index a512f93..8a0a2f9 100644 --- a/cmd/make_mysql_account.go +++ b/cmd/make_mysql_account.go @@ -18,7 +18,7 @@ type makeMysqlAccount struct{} func (makeMysqlAccount) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, ParserConfig: parser.Config{ IncludeUnknown: true, diff --git a/cmd/monday.go b/cmd/monday.go index fac52c4..9449985 100644 --- a/cmd/monday.go +++ b/cmd/monday.go @@ -21,7 +21,7 @@ type monday struct { func (monday) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "monday", Description: "Runs regular monday tasks", diff --git a/cmd/mysql.go b/cmd/mysql.go index 10b7d31..7e19a1b 100644 --- a/cmd/mysql.go +++ b/cmd/mysql.go @@ -21,7 +21,7 @@ type mysql struct { func (mysql) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, ParserConfig: parser.Config{ IncludeUnknown: true, diff --git a/cmd/provision.go b/cmd/provision.go index 2b5653e..b3a0535 100644 --- a/cmd/provision.go +++ b/cmd/provision.go @@ -20,7 +20,7 @@ type provision struct { func (provision) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "provision", Description: "Creates a new WissKI Instance", diff --git a/cmd/purge.go b/cmd/purge.go index 9d8f512..f08d3c6 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -22,7 +22,7 @@ type purge struct { func (purge) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "purge", Description: "Purges a WissKI Instance", diff --git a/cmd/rebuild.go b/cmd/rebuild.go index bcb39c9..b852a1e 100644 --- a/cmd/rebuild.go +++ b/cmd/rebuild.go @@ -20,7 +20,7 @@ type rebuild struct { func (rebuild) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "rebuild", Description: "Runs the rebuild script for several instances", diff --git a/cmd/reserve.go b/cmd/reserve.go index 415742b..0098a68 100644 --- a/cmd/reserve.go +++ b/cmd/reserve.go @@ -21,7 +21,7 @@ type reserve struct { func (reserve) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "reserve", Description: "Reserves a new WissKI Instance", diff --git a/cmd/shell.go b/cmd/shell.go index 98b3391..f4c1a3c 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -22,7 +22,7 @@ type shell struct { func (shell) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, ParserConfig: parser.Config{ IncludeUnknown: true, diff --git a/cmd/snapshot.go b/cmd/snapshot.go index 63c0c3f..0d11361 100644 --- a/cmd/snapshot.go +++ b/cmd/snapshot.go @@ -27,7 +27,7 @@ type snapshot struct { func (snapshot) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "snapshot", Description: "Generates a snapshot archive for the provided archive", diff --git a/cmd/system_update.go b/cmd/system_update.go index 8a89bc1..e622d79 100644 --- a/cmd/system_update.go +++ b/cmd/system_update.go @@ -27,7 +27,7 @@ type systemupdate struct { func (systemupdate) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, ParserConfig: parser.Config{ IncludeUnknown: true, diff --git a/cmd/update_prefix_config.go b/cmd/update_prefix_config.go index 4b46ec8..471070b 100644 --- a/cmd/update_prefix_config.go +++ b/cmd/update_prefix_config.go @@ -18,7 +18,7 @@ type updateprefixconfig struct{} func (updateprefixconfig) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: env.Requirements{ - NeedsConfig: true, + NeedsDistillery: true, }, Command: "update_prefix_config", Description: "Updates the prefix configuration", diff --git a/env/constants.go b/env/constants.go new file mode 100644 index 0000000..86f8064 --- /dev/null +++ b/env/constants.go @@ -0,0 +1,36 @@ +package env + +import ( + "os" + "path/filepath" + + "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) +} + +// UsingDistilleryExecutable checks if the current process +func (dis *Distillery) UsingDistilleryExecutable() bool { + exe, err := os.Executable() + if err != nil { + return false + } + 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) +} diff --git a/env/distillery.go b/env/distillery.go index 6045f84..6e59deb 100644 --- a/env/distillery.go +++ b/env/distillery.go @@ -54,7 +54,7 @@ func NewDistillery(params Params, req Requirements) (env *Distillery, err error) env = &Distillery{} // if we don't need to load the config, there is nothing to do - if !req.NeedsConfig { + if !req.NeedsDistillery { return } diff --git a/env/params.go b/env/params.go index 1c5e4fa..9c14222 100644 --- a/env/params.go +++ b/env/params.go @@ -20,7 +20,7 @@ func (params Params) ConfigFilePath() string { if params.BaseDirectory == "" { return "" } - return filepath.Join(params.BaseDirectory, ".env") + return filepath.Join(params.BaseDirectory, ConfigFile) } var errUnableToLoadParams = exit.Error{ diff --git a/env/requirements.go b/env/requirements.go index 2304537..66e6fed 100644 --- a/env/requirements.go +++ b/env/requirements.go @@ -6,7 +6,8 @@ import ( ) type Requirements struct { - NeedsConfig bool + // Do we need an installed distillery? + NeedsDistillery bool } // AllowsFlag checks if the provided flag may be passed to fullfill this requirement diff --git a/internal/fsx/copy.go b/internal/fsx/copy.go index a5e3c29..9d570b2 100644 --- a/internal/fsx/copy.go +++ b/internal/fsx/copy.go @@ -12,7 +12,7 @@ var ErrCopySameFile = errors.New("src and dst must be different files") // CopyFile copies a file from src to dst. // When dst and src are the same file, returns ErrCopySameFile. func CopyFile(dst, src string) error { - if src == dst { + if SameFile(src, dst) { return ErrCopySameFile } @@ -51,7 +51,7 @@ func CopyDirectory(dst, src string, onCopy func(dst, src string)) error { // TODO: Allow copying in parallel? Maybe with a mutex? // sanity checks - if src == dst { + if SameFile(src, dst) { return ErrCopySameFile } if !IsDirectory(dst) { diff --git a/internal/fsx/same.go b/internal/fsx/same.go new file mode 100644 index 0000000..5414754 --- /dev/null +++ b/internal/fsx/same.go @@ -0,0 +1,87 @@ +package fsx + +import ( + "os" + "path/filepath" +) + +// SameFile checks if path1 and path2 refer to the same file. +// If both files exist, they are compared using [os.SameFile]. +// If both files do not exist, the paths are first compared syntactically and then via recursion on [filepath.Dir]. +func SameFile(path1, path2 string) bool { + + // initial attempt: check if directly + same, certain := couldBeSameFile(path1, path2) + if certain { + return same + } + + // second attempt: find the directory names and base paths + d1, n1 := filepath.Dir(path1), filepath.Base(path1) + d2, n2 := filepath.Dir(path2), filepath.Base(path2) + + // if we have different file names (and they don't exist) + // we don't need to continue + if n1 != n2 { + return false + } + + // compare the base names! + { + same, _ := couldBeSameFile(d1, d2) + return same + } +} + +// couldBeSameFile checks if path1 might be the same as path2. +// +// If both files exist, compares using [os.SameFile]. +// Otherwise compares absolute paths using string comparison. +// +// same indicates if they might be the same file. +// authorative indiciates if the result is authorative. +func couldBeSameFile(path1, path2 string) (same, authorative bool) { + { + // stat both files + info1, err1 := os.Stat(path1) + info2, err2 := os.Stat(path2) + + // both files exist => check using os.SameFile + // the result is always authorative + if err1 == nil && err2 == nil { + same = os.SameFile(info1, info2) + authorative = true + return + } + + // only 1 file errored => they could be different + if (err1 == nil) != (err2 == nil) { + return + } + + // only 1 file does not exist => they could be different + if os.IsNotExist(err1) != os.IsNotExist(err2) { + return + } + } + + { + // resolve paths absolutely + rpath1, err1 := filepath.Abs(path1) + rpath2, err2 := filepath.Abs(path2) + + // if either path could not be resolved absolutely + // fallback to just using clean! + if err1 != nil { + rpath1 = filepath.Clean(path1) + } + if err2 != nil { + rpath2 = filepath.Clean(path2) + } + + // compare using strings + same = rpath1 == rpath2 + authorative = same // positive result is authorative! + return + } +} diff --git a/internal/fsx/touch.go b/internal/fsx/touch.go index b8cad40..4822536 100644 --- a/internal/fsx/touch.go +++ b/internal/fsx/touch.go @@ -5,7 +5,11 @@ import ( "time" ) -// Touch touches a file +// Touch touches a file. +// It is similar to the unix 'touch' command. +// +// If the file does not exist exists, it is created using [os.Create]. +// If the file does exist, it's access and modification times are updated to the current time. func Touch(path string) error { _, err := os.Stat(path) switch { diff --git a/internal/fsx/type.go b/internal/fsx/type.go index c26f31e..74c8993 100644 --- a/internal/fsx/type.go +++ b/internal/fsx/type.go @@ -1,17 +1,23 @@ +// Package fsx provides convenient abstractions to work with the filesystem. package fsx -import "os" +import ( + "os" +) +// Exists checks if the given path exists func Exists(path string) bool { _, err := os.Stat(path) return err == nil } +// IsDirectory checks if the provided path exists and is a directory func IsDirectory(path string) bool { info, err := os.Stat(path) return err == nil && info.Mode().IsDir() } +// IsFile checks if the provided path exists and is a regular file func IsFile(path string) bool { info, err := os.Stat(path) return err == nil && info.Mode().IsRegular() diff --git a/internal/stack/installable.go b/internal/stack/installable.go index ac2b57c..c607d0d 100644 --- a/internal/stack/installable.go +++ b/internal/stack/installable.go @@ -12,7 +12,7 @@ import ( ) // Installable represents a Stack that can be automatically installed from a set of resources -// See the Install() method. +// See the [Install] method. type Installable struct { Stack diff --git a/program.go b/program.go index ab270ad..3ac19e9 100644 --- a/program.go +++ b/program.go @@ -27,13 +27,25 @@ var errUserIsNotRoot = exit.Error{ Message: "This command has to be executed as root. The current user is not root.", } +var warnNoDeployWdcli = "Warning: Not using %q executable at %q. This might leave the distillery in an inconsistent state. \n" + func NewProgram() Program { return Program{ BeforeCommand: func(context Context, command Command) error { + // make sure that we are root! usr, err := user.Current() - if err != nil || usr.Uid != "0" || usr.Gid != "0" { // make sure that we are root! + if err != nil || usr.Uid != "0" || usr.Gid != "0" { return errUserIsNotRoot } + + // warn when not using the distillery excutable + if context.Description.Requirements.NeedsDistillery { + dis := context.Environment + if !dis.UsingDistilleryExecutable() { + context.EPrintf(warnNoDeployWdcli, env.Executable, dis.ExecutablePath()) + } + } + return nil },