Sybren A. Stüvel fb89658530 Refactor: replace os.IsNotExist() with errors.Is(err, fs.ErrNotExist()
`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.
2022-06-28 10:24:46 +02:00

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
}