Show a warning when using wrong executable

This commit updates the 'wdcli' command to show a warning when using the
wrong executable.
This commit is contained in:
Tom Wiesing 2022-09-09 13:35:02 +02:00
parent c4de1f2a06
commit 35bb95c5ca
No known key found for this signature in database
29 changed files with 176 additions and 30 deletions

View file

@ -25,7 +25,7 @@ type backup struct {
func (backup) Description() wisski_distillery.Description { func (backup) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "backup", Command: "backup",
Description: "Makes a backup of the entire distillery", Description: "Makes a backup of the entire distillery",

View file

@ -20,7 +20,7 @@ type blindUpdate struct {
func (blindUpdate) Description() wisski_distillery.Description { func (blindUpdate) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "blind_update", Command: "blind_update",
Description: "Runs the blind update in the provided instances", Description: "Runs the blind update in the provided instances",

View file

@ -27,7 +27,7 @@ type bootstrap struct {
func (bootstrap) Description() wisski_distillery.Description { func (bootstrap) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: false, NeedsDistillery: false,
}, },
Command: "bootstrap", Command: "bootstrap",
Description: "Bootstraps the installation of a Distillery System", 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? // TODO: Read these from the command line?
wdcliPath := filepath.Join(root, "wdcli") wdcliPath := filepath.Join(root, env.Executable)
envPath := filepath.Join(root, ".env") envPath := filepath.Join(root, env.ConfigFile)
domain := bs.Hostname domain := bs.Hostname
if domain == "" { if domain == "" {
domain = hostname.FQDN() domain = hostname.FQDN()

View file

@ -14,7 +14,7 @@ type config struct {
func (s config) Description() wisski_distillery.Description { func (s config) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "config", Command: "config",
Description: "Prints information about configuration", Description: "Prints information about configuration",

View file

@ -19,7 +19,7 @@ type cron struct {
func (cron) Description() wisski_distillery.Description { func (cron) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "cron", Command: "cron",
Description: "Runs the cron script for several instances", Description: "Runs the cron script for several instances",

View file

@ -17,7 +17,7 @@ type info struct {
func (info) Description() wisski_distillery.Description { func (info) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "info", Command: "info",
Description: "Provide information about a single repository", Description: "Provide information about a single repository",

View file

@ -16,7 +16,7 @@ type license struct{}
func (license) Description() wisski_distillery.Description { func (license) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: false, NeedsDistillery: false,
}, },
Command: "license", Command: "license",
Description: "Print licensing information about wdcli and exit. ", Description: "Print licensing information about wdcli and exit. ",

View file

@ -17,7 +17,7 @@ type ls struct {
func (ls) Description() wisski_distillery.Description { func (ls) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "ls", Command: "ls",
Description: "Lists WissKI instances", Description: "Lists WissKI instances",

View file

@ -18,7 +18,7 @@ type makeMysqlAccount struct{}
func (makeMysqlAccount) Description() wisski_distillery.Description { func (makeMysqlAccount) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
ParserConfig: parser.Config{ ParserConfig: parser.Config{
IncludeUnknown: true, IncludeUnknown: true,

View file

@ -21,7 +21,7 @@ type monday struct {
func (monday) Description() wisski_distillery.Description { func (monday) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "monday", Command: "monday",
Description: "Runs regular monday tasks", Description: "Runs regular monday tasks",

View file

@ -21,7 +21,7 @@ type mysql struct {
func (mysql) Description() wisski_distillery.Description { func (mysql) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
ParserConfig: parser.Config{ ParserConfig: parser.Config{
IncludeUnknown: true, IncludeUnknown: true,

View file

@ -20,7 +20,7 @@ type provision struct {
func (provision) Description() wisski_distillery.Description { func (provision) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "provision", Command: "provision",
Description: "Creates a new WissKI Instance", Description: "Creates a new WissKI Instance",

View file

@ -22,7 +22,7 @@ type purge struct {
func (purge) Description() wisski_distillery.Description { func (purge) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "purge", Command: "purge",
Description: "Purges a WissKI Instance", Description: "Purges a WissKI Instance",

View file

@ -20,7 +20,7 @@ type rebuild struct {
func (rebuild) Description() wisski_distillery.Description { func (rebuild) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "rebuild", Command: "rebuild",
Description: "Runs the rebuild script for several instances", Description: "Runs the rebuild script for several instances",

View file

@ -21,7 +21,7 @@ type reserve struct {
func (reserve) Description() wisski_distillery.Description { func (reserve) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "reserve", Command: "reserve",
Description: "Reserves a new WissKI Instance", Description: "Reserves a new WissKI Instance",

View file

@ -22,7 +22,7 @@ type shell struct {
func (shell) Description() wisski_distillery.Description { func (shell) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
ParserConfig: parser.Config{ ParserConfig: parser.Config{
IncludeUnknown: true, IncludeUnknown: true,

View file

@ -27,7 +27,7 @@ type snapshot struct {
func (snapshot) Description() wisski_distillery.Description { func (snapshot) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "snapshot", Command: "snapshot",
Description: "Generates a snapshot archive for the provided archive", Description: "Generates a snapshot archive for the provided archive",

View file

@ -27,7 +27,7 @@ type systemupdate struct {
func (systemupdate) Description() wisski_distillery.Description { func (systemupdate) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
ParserConfig: parser.Config{ ParserConfig: parser.Config{
IncludeUnknown: true, IncludeUnknown: true,

View file

@ -18,7 +18,7 @@ type updateprefixconfig struct{}
func (updateprefixconfig) Description() wisski_distillery.Description { func (updateprefixconfig) Description() wisski_distillery.Description {
return wisski_distillery.Description{ return wisski_distillery.Description{
Requirements: env.Requirements{ Requirements: env.Requirements{
NeedsConfig: true, NeedsDistillery: true,
}, },
Command: "update_prefix_config", Command: "update_prefix_config",
Description: "Updates the prefix configuration", Description: "Updates the prefix configuration",

36
env/constants.go vendored Normal file
View file

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

2
env/distillery.go vendored
View file

@ -54,7 +54,7 @@ func NewDistillery(params Params, req Requirements) (env *Distillery, err error)
env = &Distillery{} env = &Distillery{}
// if we don't need to load the config, there is nothing to do // if we don't need to load the config, there is nothing to do
if !req.NeedsConfig { if !req.NeedsDistillery {
return return
} }

2
env/params.go vendored
View file

@ -20,7 +20,7 @@ func (params Params) ConfigFilePath() string {
if params.BaseDirectory == "" { if params.BaseDirectory == "" {
return "" return ""
} }
return filepath.Join(params.BaseDirectory, ".env") return filepath.Join(params.BaseDirectory, ConfigFile)
} }
var errUnableToLoadParams = exit.Error{ var errUnableToLoadParams = exit.Error{

3
env/requirements.go vendored
View file

@ -6,7 +6,8 @@ import (
) )
type Requirements struct { 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 // AllowsFlag checks if the provided flag may be passed to fullfill this requirement

View file

@ -12,7 +12,7 @@ var ErrCopySameFile = errors.New("src and dst must be different files")
// CopyFile copies a file from src to dst. // CopyFile copies a file from src to dst.
// When dst and src are the same file, returns ErrCopySameFile. // When dst and src are the same file, returns ErrCopySameFile.
func CopyFile(dst, src string) error { func CopyFile(dst, src string) error {
if src == dst { if SameFile(src, dst) {
return ErrCopySameFile 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? // TODO: Allow copying in parallel? Maybe with a mutex?
// sanity checks // sanity checks
if src == dst { if SameFile(src, dst) {
return ErrCopySameFile return ErrCopySameFile
} }
if !IsDirectory(dst) { if !IsDirectory(dst) {

87
internal/fsx/same.go Normal file
View file

@ -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
}
}

View file

@ -5,7 +5,11 @@ import (
"time" "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 { func Touch(path string) error {
_, err := os.Stat(path) _, err := os.Stat(path)
switch { switch {

View file

@ -1,17 +1,23 @@
// Package fsx provides convenient abstractions to work with the filesystem.
package fsx package fsx
import "os" import (
"os"
)
// Exists checks if the given path exists
func Exists(path string) bool { func Exists(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)
return err == nil return err == nil
} }
// IsDirectory checks if the provided path exists and is a directory
func IsDirectory(path string) bool { func IsDirectory(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
return err == nil && info.Mode().IsDir() return err == nil && info.Mode().IsDir()
} }
// IsFile checks if the provided path exists and is a regular file
func IsFile(path string) bool { func IsFile(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
return err == nil && info.Mode().IsRegular() return err == nil && info.Mode().IsRegular()

View file

@ -12,7 +12,7 @@ import (
) )
// Installable represents a Stack that can be automatically installed from a set of resources // 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 { type Installable struct {
Stack Stack

View file

@ -27,13 +27,25 @@ var errUserIsNotRoot = exit.Error{
Message: "This command has to be executed as root. The current user is not root.", 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 { func NewProgram() Program {
return Program{ return Program{
BeforeCommand: func(context Context, command Command) error { BeforeCommand: func(context Context, command Command) error {
// make sure that we are root!
usr, err := user.Current() 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 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 return nil
}, },