
`os.IsNotExist()` is from before `errors.Is()` existed. The latter is the recommended approach, as it also recognised wrapped errors. No functional changes, except for recognising more cases of "does not exist" errors as such.
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package job_compilers
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var (
|
|
// embeddedScriptsFS gives access to the embedded scripts.
|
|
embeddedScriptsFS fs.FS
|
|
|
|
// onDiskScriptsFS gives access to the on-disk scripts, located in a `scripts`
|
|
// directory next to the `flamenco-manager` executable.
|
|
onDiskScriptsFS fs.FS = nil
|
|
|
|
fileLoaderInitialised = false
|
|
)
|
|
|
|
const scriptsDirName = "scripts"
|
|
|
|
// Scripts from the `./scripts` subdirectory are embedded into the executable
|
|
// here. Note that accessing these files still requires explicit use of the
|
|
// `scripts/` subdirectory, which is abstracted away by `embeddedScriptFS`.
|
|
//
|
|
//go:embed scripts
|
|
var _embeddedScriptsFS embed.FS
|
|
|
|
func initFileLoader() {
|
|
if fileLoaderInitialised {
|
|
return
|
|
}
|
|
|
|
initEmbeddedFS()
|
|
initOnDiskFS()
|
|
|
|
fileLoaderInitialised = true
|
|
}
|
|
|
|
// getAvailableFilesystems returns the filesystems to load scripts from, where
|
|
// earlier ones have priority over later ones.
|
|
func getAvailableFilesystems() []fs.FS {
|
|
filesystems := []fs.FS{}
|
|
|
|
if onDiskScriptsFS != nil {
|
|
filesystems = append(filesystems, onDiskScriptsFS)
|
|
}
|
|
|
|
filesystems = append(filesystems, embeddedScriptsFS)
|
|
return filesystems
|
|
}
|
|
|
|
// loadFileFromAnyFS iterates over the available filesystems to find the
|
|
// identified file, and returns its contents when found.
|
|
//
|
|
// Returns `os.ErrNotExist` if there is no filesystem that has this file.
|
|
func loadFileFromAnyFS(path string) ([]byte, error) {
|
|
filesystems := getAvailableFilesystems()
|
|
|
|
for _, filesystem := range filesystems {
|
|
file, err := filesystem.Open(path)
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file %s on filesystem %s: %w", path, filesystem, err)
|
|
}
|
|
return io.ReadAll(file)
|
|
}
|
|
|
|
return nil, os.ErrNotExist
|
|
}
|
|
|
|
func initEmbeddedFS() {
|
|
// Find embedded filesystem. Unless there were issues with the build of
|
|
// Flamenco Manager, this should always be here.
|
|
var err error
|
|
embeddedScriptsFS, err = fs.Sub(_embeddedScriptsFS, "scripts")
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to find embedded 'scripts' directory: %v", err))
|
|
}
|
|
}
|
|
|
|
func initOnDiskFS() {
|
|
exename, err := os.Executable()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("job compiler: unable to determine the path of the currently running executable")
|
|
return
|
|
}
|
|
logger := log.With().Str("executable", exename).Logger()
|
|
logger.Debug().Msg("job compiler: searching for scripts directory next to executable")
|
|
|
|
// Try to find the scripts next to the executable.
|
|
scriptsDir, found := findOnDiskScriptsNextTo(exename)
|
|
if found {
|
|
log.Debug().Str("scriptsDir", scriptsDir).Msg("job compiler: found scripts directory next to executable")
|
|
onDiskScriptsFS = os.DirFS(scriptsDir)
|
|
return
|
|
}
|
|
|
|
// Evaluate any symlinks and see if that produces a different path to the
|
|
// executable.
|
|
evalLinkExe, err := filepath.EvalSymlinks(exename)
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("job compiler: unable to evaluate any symlinks to the running executable")
|
|
return
|
|
}
|
|
if evalLinkExe == exename {
|
|
// Evaluating any symlinks didn't produce a different path; no need to do the same search twice.
|
|
return
|
|
}
|
|
|
|
scriptsDir, found = findOnDiskScriptsNextTo(evalLinkExe)
|
|
if !found {
|
|
logger.Debug().Msg("job compiler: did not find scripts directory next to executable")
|
|
return
|
|
}
|
|
|
|
log.Debug().Str("scriptsDir", scriptsDir).Msg("job compiler: found scripts directory next to executable")
|
|
onDiskScriptsFS = os.DirFS(scriptsDir)
|
|
}
|
|
|
|
// Find the `scripts` directory sitting next to the currently-running executable.
|
|
// Return the directory path, and a 'found' boolean indicating whether that path
|
|
// is actually a directory.
|
|
func findOnDiskScriptsNextTo(exename string) (string, bool) {
|
|
scriptsDir := filepath.Join(filepath.Dir(exename), scriptsDirName)
|
|
|
|
logger := log.With().Str("scriptsDir", scriptsDir).Logger()
|
|
logger.Trace().Msg("job compiler: finding on-disk scripts")
|
|
|
|
stat, err := os.Stat(scriptsDir)
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return scriptsDir, false
|
|
}
|
|
if err != nil {
|
|
logger.Warn().Err(err).Msg("job compiler: error accessing scripts directory")
|
|
return scriptsDir, false
|
|
}
|
|
if !stat.IsDir() {
|
|
logger.Debug().Msg("job compiler: ignoring 'scripts' next to executable; it is not a directory")
|
|
return scriptsDir, false
|
|
}
|
|
|
|
return scriptsDir, true
|
|
}
|