Add 'environment' package

This commit adds a new environment package that manages all calls to the
underlying operating system.
This commit is contained in:
Tom Wiesing 2022-09-18 14:24:22 +02:00
parent 822c70cd69
commit f19619ef9f
No known key found for this signature in database
60 changed files with 539 additions and 308 deletions

View file

@ -5,12 +5,10 @@
- Why a factory? - Why a factory?
- First steps after provisioning - First steps after provisioning
- Use `environment.Dial()` and `environment.Listen()`
- Move `provision_entrypoint.sh` into go - Move `provision_entrypoint.sh` into go
- Enhance Snapshots - Enhance Snapshots
- Export the Docker Images - Export the Docker Images
- Avoid running `docker compose` executable and shift it to a library - Avoid running `docker compose` executable and shift it to a library
- Move resolver code into this - Move resolver code into this
- Cleanup code: Have consistent error handling
- Add a proper metadata / statistics server
- Single Malt Mode: Support having a single instance only! - Single Malt Mode: Support having a single instance only!

View file

@ -1,12 +1,10 @@
package cmd package cmd
import ( import (
"io/fs"
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/backup" "github.com/FAU-CDI/wisski-distillery/internal/backup"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/FAU-CDI/wisski-distillery/pkg/targz" "github.com/FAU-CDI/wisski-distillery/pkg/targz"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
@ -59,7 +57,7 @@ func (bk backupC) Run(context wisski_distillery.Context) error {
} }
defer func() { defer func() {
logging.LogMessage(context.IOStream, "Removing snapshot staging directory") logging.LogMessage(context.IOStream, "Removing snapshot staging directory")
os.RemoveAll(sPath) dis.Environment.RemoveAll(sPath)
}() }()
} else { } else {
// staging mode: use dest as a destination // staging mode: use dest as a destination
@ -73,8 +71,8 @@ func (bk backupC) Run(context wisski_distillery.Context) error {
// create the directory (if it doesn't already exist) // create the directory (if it doesn't already exist)
logging.LogMessage(context.IOStream, "Creating staging directory") logging.LogMessage(context.IOStream, "Creating staging directory")
err = os.Mkdir(sPath, fs.ModePerm) err = dis.Core.Environment.Mkdir(sPath, environment.DefaultDirPerm)
if !os.IsExist(err) && err != nil { if !environment.IsExist(err) && err != nil {
return errSnapshotFailed.WithMessageF(err) return errSnapshotFailed.WithMessageF(err)
} }
err = nil err = nil
@ -86,7 +84,7 @@ func (bk backupC) Run(context wisski_distillery.Context) error {
Dest: sPath, Dest: sPath,
Auto: bk.Positionals.Dest == "", Auto: bk.Positionals.Dest == "",
}) })
backup.WriteReport(context.IOStream) backup.WriteReport(dis.Core.Environment, context.IOStream)
return nil return nil
}, context.IOStream, "Generating Backup") }, context.IOStream, "Generating Backup")
@ -108,7 +106,7 @@ func (bk backupC) Run(context wisski_distillery.Context) error {
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
context.IOStream.Println(archivePath) context.IOStream.Println(archivePath)
count, err = targz.Package(archivePath, sPath, func(dst, src string) { count, err = targz.Package(dis.Core.Environment, archivePath, sPath, func(dst, src string) {
context.Printf("\033[2K\r%s", dst) context.Printf("\033[2K\r%s", dst)
}) })
context.Println("") context.Println("")

View file

@ -3,7 +3,7 @@ package cmd
import ( import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/execx" "github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
) )
@ -47,7 +47,7 @@ func (bu blindUpdate) Run(context wisski_distillery.Context) error {
code, err := instance.Shell(context.IOStream, "/runtime/blind_update.sh") code, err := instance.Shell(context.IOStream, "/runtime/blind_update.sh")
if err != nil { if err != nil {
return errBlindUpdateFailed.WithMessageF(instance.Slug, execx.ExecCommandError) return errBlindUpdateFailed.WithMessageF(instance.Slug, environment.ExecCommandError)
} }
if code != 0 { if code != 0 {
return errBlindUpdateFailed.WithMessageF(instance.Slug, code) return errBlindUpdateFailed.WithMessageF(instance.Slug, code)

View file

@ -2,12 +2,12 @@ package cmd
import ( import (
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
@ -67,11 +67,15 @@ var errBootstrapCreateFile = exit.Error{
} }
func (bs bootstrap) Run(context wisski_distillery.Context) error { func (bs bootstrap) Run(context wisski_distillery.Context) error {
// installation environment is the native environment!
// TODO: Should this be configurable?
var env environment.Native
root := bs.Directory root := bs.Directory
// check that we didn't get a different base directory // check that we didn't get a different base directory
{ {
got, err := core.ReadBaseDirectory() got, err := core.ReadBaseDirectory(env)
if err == nil && got != "" && got != root { if err == nil && got != "" && got != root {
return errBootstrapDifferent.WithMessageF(got) return errBootstrapDifferent.WithMessageF(got)
} }
@ -79,10 +83,10 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
{ {
logging.LogMessage(context.IOStream, "Creating root deployment directory") logging.LogMessage(context.IOStream, "Creating root deployment directory")
if err := os.MkdirAll(root, fs.ModeDir); err != nil { if err := env.MkdirAll(root, environment.DefaultDirPerm); err != nil {
return errBootstrapFailedToCreateDirectory.WithMessageF(root) return errBootstrapFailedToCreateDirectory.WithMessageF(root)
} }
if err := core.WriteBaseDirectory(root); err != nil { if err := core.WriteBaseDirectory(env, root); err != nil {
return errBootstrapFailedToSaveDirectory.WithMessageF(root) return errBootstrapFailedToSaveDirectory.WithMessageF(root)
} }
context.Println(root) context.Println(root)
@ -98,18 +102,18 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
tpl.DefaultDomain = bs.Hostname tpl.DefaultDomain = bs.Hostname
// and use thge defaults // and use thge defaults
if err := tpl.SetDefaults(); err != nil { if err := tpl.SetDefaults(env); err != nil {
return errBootstrapWriteConfig.WithMessageF(err) return errBootstrapWriteConfig.WithMessageF(err)
} }
{ {
logging.LogMessage(context.IOStream, "Copying over wdcli executable") logging.LogMessage(context.IOStream, "Copying over wdcli executable")
exe, err := os.Executable() exe, err := env.Executable()
if err != nil { if err != nil {
return errBoostrapFailedToCopyExe.WithMessageF(err) return errBoostrapFailedToCopyExe.WithMessageF(err)
} }
err = fsx.CopyFile(wdcliPath, exe) err = fsx.CopyFile(env, wdcliPath, exe)
if err != nil && err != fsx.ErrCopySameFile { if err != nil && err != fsx.ErrCopySameFile {
return errBoostrapFailedToCopyExe.WithMessageF(err) return errBoostrapFailedToCopyExe.WithMessageF(err)
} }
@ -117,9 +121,9 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
} }
{ {
if !fsx.IsFile(envPath) { if !fsx.IsFile(env, envPath) {
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
env, err := os.Create(envPath) env, err := env.Create(envPath, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
@ -133,7 +137,8 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
context.Println(tpl.SelfOverridesFile) context.Println(tpl.SelfOverridesFile)
if err := os.WriteFile( if err := environment.WriteFile(
env,
tpl.SelfOverridesFile, tpl.SelfOverridesFile,
core.DefaultOverridesJSON, core.DefaultOverridesJSON,
fs.ModePerm, fs.ModePerm,
@ -142,7 +147,8 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
} }
context.Println(tpl.AuthorizedKeys) context.Println(tpl.AuthorizedKeys)
if err := os.WriteFile( if err := environment.WriteFile(
env,
tpl.AuthorizedKeys, tpl.AuthorizedKeys,
core.DefaultAuthorizedKeys, core.DefaultAuthorizedKeys,
fs.ModePerm, fs.ModePerm,
@ -160,14 +166,14 @@ func (bs bootstrap) Run(context wisski_distillery.Context) error {
// re-read the configuration and print it! // re-read the configuration and print it!
logging.LogMessage(context.IOStream, "Configuration is now complete") logging.LogMessage(context.IOStream, "Configuration is now complete")
f, err := os.Open(envPath) f, err := env.Open(envPath)
if err != nil { if err != nil {
return errBootstrapOpenConfig.WithMessageF(err) return errBootstrapOpenConfig.WithMessageF(err)
} }
defer f.Close() defer f.Close()
var cfg config.Config var cfg config.Config
if err := cfg.Unmarshal(f); err != nil { if err := cfg.Unmarshal(env, f); err != nil {
return errBootstrapOpenConfig.WithMessageF(err) return errBootstrapOpenConfig.WithMessageF(err)
} }
context.Println(cfg) context.Println(cfg)

View file

@ -1,10 +1,10 @@
package cmd package cmd
import ( import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
) )
@ -29,13 +29,10 @@ func (monday) Description() wisski_distillery.Description {
} }
func (monday monday) AfterParse() error { func (monday monday) AfterParse() error {
_, err := os.Stat(monday.Positionals.GraphdbZip) // TODO: Use a generic environment here!
if os.IsNotExist(err) { if !fsx.IsFile(environment.Native{}, monday.Positionals.GraphdbZip) {
return errNoGraphDBZip.WithMessageF(monday.Positionals.GraphdbZip) return errNoGraphDBZip.WithMessageF(monday.Positionals.GraphdbZip)
} }
if err != nil {
return err
}
return nil return nil
} }

View file

@ -57,7 +57,7 @@ func (p provision) Run(context wisski_distillery.Context) error {
// check that the base directory does not exist // check that the base directory does not exist
logging.LogMessage(context.IOStream, "Checking that base directory %s does not exist", instance.FilesystemBase) logging.LogMessage(context.IOStream, "Checking that base directory %s does not exist", instance.FilesystemBase)
if fsx.IsDirectory(instance.FilesystemBase) { if fsx.IsDirectory(dis.Environment, instance.FilesystemBase) {
return errProvisionAlreadyExists.WithMessageF(slug) return errProvisionAlreadyExists.WithMessageF(slug)
} }

View file

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
@ -73,7 +71,7 @@ func (p purge) Run(context wisski_distillery.Context) error {
// remove the filesystem // remove the filesystem
logging.LogMessage(context.IOStream, "Removing from filesystem %s", instance.FilesystemBase) logging.LogMessage(context.IOStream, "Removing from filesystem %s", instance.FilesystemBase)
if err := os.RemoveAll(instance.FilesystemBase); err != nil { if err := dis.Core.Environment.RemoveAll(instance.FilesystemBase); err != nil {
context.EPrintln(err) context.EPrintln(err)
} }

View file

@ -33,7 +33,9 @@ var errRebuildFailed = exit.Error{
} }
func (rb rebuild) Run(context wisski_distillery.Context) error { func (rb rebuild) Run(context wisski_distillery.Context) error {
instances, err := context.Environment.Instances().Load(rb.Positionals.Slug...) dis := context.Environment
instances, err := dis.Instances().Load(rb.Positionals.Slug...)
if err != nil { if err != nil {
return err return err
} }
@ -44,7 +46,7 @@ func (rb rebuild) Run(context wisski_distillery.Context) error {
logging.LogOperation(func() error { logging.LogOperation(func() error {
s := instance.Barrel() s := instance.Barrel()
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
return s.Install(context.IOStream, component.InstallationContext{}) return s.Install(dis.Core.Environment, context.IOStream, component.InstallationContext{})
}, context.IOStream, "Installing docker stack"); err != nil { }, context.IOStream, "Installing docker stack"); err != nil {
globalErr = err globalErr = err
return err return err

View file

@ -58,7 +58,7 @@ func (r reserve) Run(context wisski_distillery.Context) error {
// check that the base directory does not exist // check that the base directory does not exist
logging.LogMessage(context.IOStream, "Checking that base directory %s does not exist", instance.FilesystemBase) logging.LogMessage(context.IOStream, "Checking that base directory %s does not exist", instance.FilesystemBase)
if fsx.IsDirectory(instance.FilesystemBase) { if fsx.IsDirectory(dis.Environment, instance.FilesystemBase) {
return errProvisionAlreadyExists.WithMessageF(slug) return errProvisionAlreadyExists.WithMessageF(slug)
} }
@ -66,7 +66,7 @@ func (r reserve) Run(context wisski_distillery.Context) error {
s := instance.Reserve() s := instance.Reserve()
{ {
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
return s.Install(context.IOStream, component.InstallationContext{}) return s.Install(dis.Core.Environment, context.IOStream, component.InstallationContext{})
}, context.IOStream, "Installing docker stack"); err != nil { }, context.IOStream, "Installing docker stack"); err != nil {
return err return err
} }

View file

@ -2,11 +2,11 @@ package cmd
import ( import (
"io/fs" "io/fs"
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/FAU-CDI/wisski-distillery/pkg/targz" "github.com/FAU-CDI/wisski-distillery/pkg/targz"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
@ -60,7 +60,7 @@ func (bi snapshot) Run(context wisski_distillery.Context) error {
} }
defer func() { defer func() {
logging.LogMessage(context.IOStream, "Removing snapshot staging directory") logging.LogMessage(context.IOStream, "Removing snapshot staging directory")
os.RemoveAll(sPath) dis.Core.Environment.RemoveAll(sPath)
}() }()
} else { } else {
// staging mode: use dest as a destination // staging mode: use dest as a destination
@ -74,8 +74,8 @@ func (bi snapshot) Run(context wisski_distillery.Context) error {
// create the directory (if it doesn't already exist) // create the directory (if it doesn't already exist)
logging.LogMessage(context.IOStream, "Creating staging directory") logging.LogMessage(context.IOStream, "Creating staging directory")
err = os.Mkdir(sPath, fs.ModePerm) err = dis.Core.Environment.Mkdir(sPath, fs.ModePerm)
if !os.IsExist(err) && err != nil { if !environment.IsExist(err) && err != nil {
return errSnapshotFailed.WithMessageF(err) return errSnapshotFailed.WithMessageF(err)
} }
err = nil err = nil
@ -92,7 +92,7 @@ func (bi snapshot) Run(context wisski_distillery.Context) error {
}) })
// write out the report, ignoring any errors! // write out the report, ignoring any errors!
sreport.WriteReport(context.IOStream) sreport.WriteReport(dis.Core.Environment, context.IOStream)
return nil return nil
}, context.IOStream, "Generating Snapshot") }, context.IOStream, "Generating Snapshot")
@ -115,7 +115,7 @@ func (bi snapshot) Run(context wisski_distillery.Context) error {
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
context.IOStream.Println(archivePath) context.IOStream.Println(archivePath)
count, err = targz.Package(archivePath, sPath, func(dst, src string) { count, err = targz.Package(dis.Core.Environment, archivePath, sPath, func(dst, src string) {
context.Printf("\033[2K\r%s", dst) context.Printf("\033[2K\r%s", dst)
}) })
context.Println("") context.Println("")

View file

@ -1,13 +1,12 @@
package cmd package cmd
import ( import (
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/execx" "github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/FAU-CDI/wisski-distillery/pkg/unpack" "github.com/FAU-CDI/wisski-distillery/pkg/unpack"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
@ -43,13 +42,10 @@ var errNoGraphDBZip = exit.Error{
} }
func (s systemupdate) AfterParse() error { func (s systemupdate) AfterParse() error {
_, err := os.Stat(s.Positionals.GraphdbZip) // TODO: Use a generic environment here!
if os.IsNotExist(err) { if !fsx.IsFile(environment.Native{}, s.Positionals.GraphdbZip) {
return errNoGraphDBZip.WithMessageF(s.Positionals.GraphdbZip) return errNoGraphDBZip.WithMessageF(s.Positionals.GraphdbZip)
} }
if err != nil {
return err
}
return nil return nil
} }
@ -85,7 +81,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
dis.SnapshotsArchivePath(), dis.SnapshotsArchivePath(),
} { } {
context.Println(d) context.Println(d)
if err := os.MkdirAll(d, os.ModeDir); err != nil { if err := dis.Core.Environment.MkdirAll(d, environment.DefaultDirPerm); err != nil {
return errBoostrapFailedToCreateDirectory.WithMessageF(d, err) return errBoostrapFailedToCreateDirectory.WithMessageF(d, err)
} }
} }
@ -123,10 +119,10 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
for _, component := range dis.Installables() { for _, component := range dis.Installables() {
stack := component.Stack() stack := component.Stack(dis.Core.Environment)
ctx := component.Context(ctx) ctx := component.Context(ctx)
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
return stack.Install(context.IOStream, ctx) return stack.Install(dis.Core.Environment, context.IOStream, ctx)
}, context.IOStream, "Installing docker stack %q", component.Name()); err != nil { }, context.IOStream, "Installing docker stack %q", component.Name()); err != nil {
return err return err
} }
@ -143,7 +139,7 @@ func (si systemupdate) Run(context wisski_distillery.Context) error {
} }
if err := logging.LogOperation(func() error { if err := logging.LogOperation(func() error {
return unpack.InstallDir(dis.Config.RuntimeDir(), "runtime", config.Runtime, func(dst, src string) { return unpack.InstallDir(dis.Core.Environment, dis.Config.RuntimeDir(), "runtime", config.Runtime, func(dst, src string) {
context.Printf("[copy] %s\n", dst) context.Printf("[copy] %s\n", dst)
}) })
}, context.IOStream, "Unpacking Runtime Components"); err != nil { }, context.IOStream, "Unpacking Runtime Components"); err != nil {
@ -175,10 +171,11 @@ var errMustExecFailed = exit.Error{
// mustExec indicates that the given executable process must complete successfully. // mustExec indicates that the given executable process must complete successfully.
// If it does not, returns errMustExecFailed // If it does not, returns errMustExecFailed
func (si systemupdate) mustExec(context wisski_distillery.Context, workdir string, exe string, argv ...string) error { func (si systemupdate) mustExec(context wisski_distillery.Context, workdir string, exe string, argv ...string) error {
dis := context.Environment
if workdir == "" { if workdir == "" {
workdir = context.Environment.Config.DeployRoot workdir = context.Environment.Config.DeployRoot
} }
code := execx.Exec(context.IOStream, workdir, exe, argv...) code := dis.Core.Environment.Exec(context.IOStream, workdir, exe, argv...)
if code != 0 { if code != 0 {
err := errMustExecFailed.WithMessageF(code) err := errMustExecFailed.WithMessageF(code)
err.ExitCode = exit.ExitCode(code) err.ExitCode = exit.ExitCode(code)

View file

@ -1,11 +1,11 @@
package cmd package cmd
import ( import (
"io/fs" "io"
"os"
wisski_distillery "github.com/FAU-CDI/wisski-distillery" wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
) )
@ -42,7 +42,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
target := ddis.ResolverConfigPath() target := ddis.ResolverConfigPath()
// print the configuration // print the configuration
config, err := os.OpenFile(target, os.O_WRONLY, fs.ModePerm) config, err := dis.Core.Environment.Create(target, environment.DefaultFilePerm)
if err != nil { if err != nil {
return errPrefixUpdateFailed.WithMessageF(err) return errPrefixUpdateFailed.WithMessageF(err)
} }
@ -58,7 +58,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
context.IOStream.Printf("%s", data) context.IOStream.Printf("%s", data)
// and write it out! // and write it out!
if _, err := config.WriteString(data); err != nil { if _, err := io.WriteString(config, data); err != nil {
return err return err
} }
@ -70,7 +70,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
// and restart the resolver to apply the config! // and restart the resolver to apply the config!
logging.LogMessage(context.IOStream, "restarting resolver stack") logging.LogMessage(context.IOStream, "restarting resolver stack")
if err := ddis.Stack().Restart(context.IOStream); err != nil { if err := ddis.Stack(ddis.Environment).Restart(context.IOStream); err != nil {
return errPrefixUpdateFailed.WithMessageF(err) return errPrefixUpdateFailed.WithMessageF(err)
} }

View file

@ -2,14 +2,13 @@
package backup package backup
import ( import (
"io/fs"
"os"
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
@ -61,6 +60,7 @@ func (backup *Backup) run(io stream.IOStream, dis *wisski.Distillery) {
err: bc.Backup(context), err: bc.Backup(context),
} }
}(bc, &context{ }(bc, &context{
env: dis.Core.Environment,
io: io, io: io,
dst: filepath.Join(backup.Description.Dest, bc.BackupName()), dst: filepath.Join(backup.Description.Dest, bc.BackupName()),
files: files, files: files,
@ -73,7 +73,7 @@ func (backup *Backup) run(io stream.IOStream, dis *wisski.Distillery) {
defer wg.Done() defer wg.Done()
instancesBackupDir := filepath.Join(backup.Description.Dest, "instances") instancesBackupDir := filepath.Join(backup.Description.Dest, "instances")
if err := os.Mkdir(instancesBackupDir, fs.ModeDir); err != nil { if err := dis.Core.Environment.Mkdir(instancesBackupDir, environment.DefaultDirPerm); err != nil {
backup.InstanceListErr = err backup.InstanceListErr = err
return return
} }
@ -89,7 +89,7 @@ func (backup *Backup) run(io stream.IOStream, dis *wisski.Distillery) {
for i, instance := range instances { for i, instance := range instances {
backup.InstanceSnapshots[i] = func() wisski.Snapshot { backup.InstanceSnapshots[i] = func() wisski.Snapshot {
dir := filepath.Join(instancesBackupDir, instance.Slug) dir := filepath.Join(instancesBackupDir, instance.Slug)
if err := os.Mkdir(dir, fs.ModeDir); err != nil { if err := dis.Core.Environment.Mkdir(dir, environment.DefaultDirPerm); err != nil {
return wisski.Snapshot{ return wisski.Snapshot{
ErrPanic: err, ErrPanic: err,
} }

View file

@ -3,16 +3,16 @@ package backup
import ( import (
"errors" "errors"
"io" "io"
"io/fs"
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
// context implements [components.BackupContext] // context implements [components.BackupContext]
type context struct { type context struct {
env environment.Environment
io stream.IOStream io stream.IOStream
dst string // destination directory dst string // destination directory
files chan string // files channel files chan string // files channel
@ -54,7 +54,7 @@ func (bc *context) AddDirectory(path string, op func() error) error {
} }
// run the make directory // run the make directory
if err := os.Mkdir(dst, fs.ModeDir); err != nil { if err := bc.env.Mkdir(dst, environment.DefaultDirPerm); err != nil {
return err return err
} }
@ -72,7 +72,7 @@ func (bc *context) CopyFile(dst, src string) error {
return err return err
} }
bc.sendPath(dst) bc.sendPath(dst)
return fsx.CopyFile(dstPath, src) return fsx.CopyFile(bc.env, dstPath, src)
} }
func (bc *context) AddFile(path string, op func(file io.Writer) error) error { func (bc *context) AddFile(path string, op func(file io.Writer) error) error {
@ -83,7 +83,7 @@ func (bc *context) AddFile(path string, op func(file io.Writer) error) error {
} }
// create the file // create the file
file, err := os.Create(dst) file, err := bc.env.Create(dst, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }

View file

@ -4,13 +4,13 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/pkg/countwriter" "github.com/FAU-CDI/wisski-distillery/pkg/countwriter"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
@ -96,20 +96,20 @@ func (backup Backup) Report(w io.Writer) (int, error) {
// WriteReport writes out the report belonging to this backup. // WriteReport writes out the report belonging to this backup.
// It is a separate function, to allow writing it indepenently of the rest. // It is a separate function, to allow writing it indepenently of the rest.
func (backup Backup) WriteReport(io stream.IOStream) error { func (backup Backup) WriteReport(env environment.Environment, stream stream.IOStream) error {
return logging.LogOperation(func() error { return logging.LogOperation(func() error {
reportPath := filepath.Join(backup.Description.Dest, "report.txt") reportPath := filepath.Join(backup.Description.Dest, "report.txt")
io.Println(reportPath) stream.Println(reportPath)
// create the report file! // create the report file!
report, err := os.Create(reportPath) report, err := env.Create(reportPath, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
defer report.Close() defer report.Close()
// print the report into it! // print the report into it!
_, err = report.WriteString(backup.String()) _, err = io.WriteString(report, backup.String())
return err return err
}, io, "Writing backup report") }, stream, "Writing backup report")
} }

View file

@ -28,8 +28,8 @@ type BackupContext interface {
// It then allows op to fill the file. // It then allows op to fill the file.
AddDirectory(path string, op func() error) error AddDirectory(path string, op func() error) error
// CopyFile copies a file from source to dst. // CopyFile copies a file from src to dst.
CopyFile(dest, src string) error CopyFile(dst, src string) error
// AddFile creates a new file at the provided path inside the destination. // AddFile creates a new file at the provided path inside the destination.
// Passing the empty path creates the destination as a file. // Passing the empty path creates the destination as a file.

View file

@ -3,6 +3,7 @@ package component
import ( import (
"github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Component represents a logical subsystem of the distillery. // Component represents a logical subsystem of the distillery.
@ -38,7 +39,7 @@ type Installable interface {
// Stack can be used to gain access to the "docker compose" stack. // Stack can be used to gain access to the "docker compose" stack.
// //
// This should internally call [ComponentBase.MakeStack] // This should internally call [ComponentBase.MakeStack]
Stack() StackWithResources Stack(env environment.Environment) StackWithResources
// Context returns a new InstallationContext to be used during installation from the command line. // Context returns a new InstallationContext to be used during installation from the command line.
// Typically this should just pass through the parent, but might perform other tasks. // Typically this should just pass through the parent, but might perform other tasks.
@ -47,8 +48,14 @@ type Installable interface {
// ComponentBase implements base functionality for a component // ComponentBase implements base functionality for a component
type ComponentBase struct { type ComponentBase struct {
Dir string // Dir is the directory this component lives in Core // the core of the associated distillery
Config *config.Config // Config is the configuration of the underlying distillery Dir string // Dir is the directory this component lives in
}
// Core represents the core of a distillery
type Core struct {
Environment environment.Environment // environment to use for reading / writing to and from the distillery
Config *config.Config // the configuration of the distillery
} }
// Base returns a reference to the ComponentBase // Base returns a reference to the ComponentBase
@ -67,7 +74,8 @@ func (ComponentBase) Context(parent InstallationContext) InstallationContext {
} }
// MakeStack registers the Installable as a stack // MakeStack registers the Installable as a stack
func (cb ComponentBase) MakeStack(stack StackWithResources) StackWithResources { func (cb ComponentBase) MakeStack(env environment.Environment, stack StackWithResources) StackWithResources {
stack.Env = env
stack.Dir = cb.Dir stack.Dir = cb.Dir
return stack return stack
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Control represents the control server // Control represents the control server
@ -24,8 +25,8 @@ func (control Control) Name() string {
//go:embed all:control control.env //go:embed all:control control.env
var resources embed.FS var resources embed.FS
func (control Control) Stack() component.StackWithResources { func (control Control) Stack(env environment.Environment) component.StackWithResources {
return control.ComponentBase.MakeStack(component.StackWithResources{ return control.ComponentBase.MakeStack(env, component.StackWithResources{
Resources: resources, Resources: resources,
ContextPath: "control", ContextPath: "control",
EnvPath: "control.env", EnvPath: "control.env",
@ -49,6 +50,6 @@ func (control Control) Stack() component.StackWithResources {
func (control Control) Context(parent component.InstallationContext) component.InstallationContext { func (control Control) Context(parent component.InstallationContext) component.InstallationContext {
return component.InstallationContext{ return component.InstallationContext{
core.Executable: control.Config.CurrentExecutable(), core.Executable: control.Config.CurrentExecutable(control.Environment), // TODO: Does this make sense?
} }
} }

View file

@ -2,7 +2,6 @@ package control
import ( import (
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -37,7 +36,7 @@ func (control Control) resolver(io stream.IOStream) (p wdresolve.ResolveHandler,
// open the prefix file // open the prefix file
prefixFile := control.ResolverConfigPath() prefixFile := control.ResolverConfigPath()
fs, err := os.Open(prefixFile) fs, err := control.Environment.Open(prefixFile)
io.Println("loading prefixes from ", prefixFile) io.Println("loading prefixes from ", prefixFile)
if err != nil { if err != nil {
return p, err return p, err

View file

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strings" "strings"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
@ -13,7 +12,7 @@ import (
// self returns the handler for the self overrides // self returns the handler for the self overrides
func (control Control) self(io stream.IOStream) (redirect Redirect, err error) { func (control Control) self(io stream.IOStream) (redirect Redirect, err error) {
// open the overrides file // open the overrides file
overrides, err := os.Open(control.Config.SelfOverridesFile) overrides, err := control.Environment.Open(control.Config.SelfOverridesFile)
io.Printf("loading overrides from %q\n", control.Config.SelfOverridesFile) io.Printf("loading overrides from %q\n", control.Config.SelfOverridesFile)
if err != nil { if err != nil {
return redirect, err return redirect, err

View file

@ -2,9 +2,9 @@ package component
import ( import (
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/FAU-CDI/wisski-distillery/pkg/unpack" "github.com/FAU-CDI/wisski-distillery/pkg/unpack"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -29,7 +29,7 @@ type StackWithResources struct {
CopyContextFiles []string // Files to copy from the installation context CopyContextFiles []string // Files to copy from the installation context
MakeDirsPerm fs.FileMode // permission for diretories, defaults to fs.ModeDir MakeDirsPerm fs.FileMode // permission for diretories, defaults to [environment.DefaultDirCreate]
MakeDirs []string // directories to ensure that exist MakeDirs []string // directories to ensure that exist
TouchFiles []string // Files to 'touch', i.e. ensure that exist; guaranteed to be run after MakeDirs TouchFiles []string // Files to 'touch', i.e. ensure that exist; guaranteed to be run after MakeDirs
@ -42,10 +42,11 @@ type InstallationContext map[string]string
// //
// Installation is non-interactive, but will provide debugging output onto io. // Installation is non-interactive, but will provide debugging output onto io.
// InstallationContext // InstallationContext
func (is StackWithResources) Install(io stream.IOStream, context InstallationContext) error { func (is StackWithResources) Install(env environment.Environment, io stream.IOStream, context InstallationContext) error {
if is.ContextPath != "" { if is.ContextPath != "" {
// setup the base files // setup the base files
if err := unpack.InstallDir( if err := unpack.InstallDir(
env,
is.Dir, is.Dir,
is.ContextPath, is.ContextPath,
is.Resources, is.Resources,
@ -62,6 +63,7 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon
if is.EnvPath != "" && is.EnvContext != nil { if is.EnvPath != "" && is.EnvContext != nil {
io.Printf("[config] %s\n", envDest) io.Printf("[config] %s\n", envDest)
if err := unpack.InstallTemplate( if err := unpack.InstallTemplate(
env,
envDest, envDest,
is.EnvContext, is.EnvContext,
is.EnvPath, is.EnvPath,
@ -78,9 +80,9 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon
io.Printf("[make] %s\n", dst) io.Printf("[make] %s\n", dst)
if is.MakeDirsPerm == fs.FileMode(0) { if is.MakeDirsPerm == fs.FileMode(0) {
is.MakeDirsPerm = fs.ModeDir is.MakeDirsPerm = environment.DefaultDirPerm
} }
if err := os.MkdirAll(dst, is.MakeDirsPerm); err != nil { if err := env.MkdirAll(dst, is.MakeDirsPerm); err != nil {
return err return err
} }
} }
@ -98,7 +100,7 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon
// copy over file from context // copy over file from context
io.Printf("[copy] %s (from %s)\n", dst, src) io.Printf("[copy] %s (from %s)\n", dst, src)
if err := fsx.CopyFile(dst, src); err != nil { if err := fsx.CopyFile(env, dst, src); err != nil {
return errors.Wrapf(err, "Unable to copy file %s", src) return errors.Wrapf(err, "Unable to copy file %s", src)
} }
} }
@ -109,7 +111,7 @@ func (is StackWithResources) Install(io stream.IOStream, context InstallationCon
dst := filepath.Join(is.Dir, name) dst := filepath.Join(is.Dir, name)
io.Printf("[touch] %s\n", dst) io.Printf("[touch] %s\n", dst)
if err := fsx.Touch(dst); err != nil { if err := fsx.Touch(env, dst); err != nil {
return err return err
} }
} }

View file

@ -21,7 +21,7 @@ func (instances *Instances) Create(slug string) (wisski WissKI, err error) {
wisski.instances = instances wisski.instances = instances
// make sure that the slug is valid! // make sure that the slug is valid!
slug, err = stringparser.ParseSlug(slug) slug, err = stringparser.ParseSlug(instances.Environment, slug)
if err != nil { if err != nil {
return wisski, errInvalidSlug return wisski, errInvalidSlug
} }
@ -70,7 +70,7 @@ func (wisski WissKI) Provision(io stream.IOStream) error {
// create the basic st! // create the basic st!
st := wisski.Barrel() st := wisski.Barrel()
if err := st.Install(io, component.InstallationContext{}); err != nil { if err := st.Install(wisski.instances.Core.Environment, io, component.InstallationContext{}); err != nil {
return err return err
} }

View file

@ -2,12 +2,11 @@ package instances
import ( import (
"fmt" "fmt"
"io/fs"
"os"
"path/filepath" "path/filepath"
_ "embed" _ "embed"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
@ -51,7 +50,7 @@ func (wisski *WissKI) ExportPathbuilders(dest string) error {
for _, name := range names { for _, name := range names {
pbxml := []byte(pathbuilders[name]) pbxml := []byte(pathbuilders[name])
name := filepath.Join(dest, fmt.Sprintf("%s.xml", name)) name := filepath.Join(dest, fmt.Sprintf("%s.xml", name))
if err := os.WriteFile(name, pbxml, fs.ModePerm); err != nil { if err := environment.WriteFile(wisski.instances.Core.Environment, name, pbxml, environment.DefaultFilePerm); err != nil {
return err return err
} }
} }

View file

@ -3,7 +3,6 @@ package instances
import ( import (
"errors" "errors"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -14,7 +13,7 @@ import (
// NoPrefix checks if this WissKI instance is excluded from generating prefixes. // NoPrefix checks if this WissKI instance is excluded from generating prefixes.
// TODO: Move this to the database! // TODO: Move this to the database!
func (wisski *WissKI) NoPrefix() bool { func (wisski *WissKI) NoPrefix() bool {
return fsx.IsFile(filepath.Join(wisski.FilesystemBase, "prefixes.skip")) return fsx.IsFile(wisski.instances.Environment, filepath.Join(wisski.FilesystemBase, "prefixes.skip"))
} }
var errPrefixExecFailed = errors.New("PrefixConfig: Failed to call list_uri_prefixes") var errPrefixExecFailed = errors.New("PrefixConfig: Failed to call list_uri_prefixes")
@ -41,8 +40,8 @@ func (wisski *WissKI) PrefixConfig() (config string, err error) {
// custom prefixes // custom prefixes
prefixPath := filepath.Join(wisski.FilesystemBase, "prefixes") prefixPath := filepath.Join(wisski.FilesystemBase, "prefixes")
if fsx.IsFile(prefixPath) { if fsx.IsFile(wisski.instances.Environment, prefixPath) {
prefix, err := os.Open(prefixPath) prefix, err := wisski.instances.Core.Environment.Open(prefixPath)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -2,7 +2,6 @@ package instances
import ( import (
"embed" "embed"
"io/fs"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
@ -16,6 +15,7 @@ func (wisski WissKI) Barrel() component.StackWithResources {
return component.StackWithResources{ return component.StackWithResources{
Stack: component.Stack{ Stack: component.Stack{
Dir: wisski.FilesystemBase, Dir: wisski.FilesystemBase,
Env: wisski.instances.Environment,
}, },
Resources: barrelResources, Resources: barrelResources,
@ -35,8 +35,7 @@ func (wisski WissKI) Barrel() component.StackWithResources {
"GLOBAL_AUTHORIZED_KEYS_FILE": wisski.instances.Config.GlobalAuthorizedKeysFile, "GLOBAL_AUTHORIZED_KEYS_FILE": wisski.instances.Config.GlobalAuthorizedKeysFile,
}, },
MakeDirsPerm: fs.ModeDir | fs.ModePerm, MakeDirs: []string{"data", ".composer"},
MakeDirs: []string{"data", ".composer"},
TouchFiles: []string{ TouchFiles: []string{
filepath.Join("data", "authorized_keys"), filepath.Join("data", "authorized_keys"),
@ -52,6 +51,7 @@ func (wisski WissKI) Reserve() component.StackWithResources {
return component.StackWithResources{ return component.StackWithResources{
Stack: component.Stack{ Stack: component.Stack{
Dir: wisski.FilesystemBase, Dir: wisski.FilesystemBase,
Env: wisski.instances.Environment,
}, },
Resources: reserveResources, Resources: reserveResources,

View file

@ -17,7 +17,7 @@ func (*SQL) BackupName() string {
func (sql *SQL) Backup(context component.BackupContext) error { func (sql *SQL) Backup(context component.BackupContext) error {
return context.AddFile("", func(file io.Writer) error { return context.AddFile("", func(file io.Writer) error {
io := context.IO().Streams(file, nil, nil, 0).NonInteractive() io := context.IO().Streams(file, nil, nil, 0).NonInteractive()
code, err := sql.Stack().Exec(io, "sql", "mysqldump", "--all-databases") code, err := sql.Stack(sql.Environment).Exec(io, "sql", "mysqldump", "--all-databases")
if err != nil { if err != nil {
return err return err
} }

View file

@ -21,6 +21,7 @@ func (sql SQL) openDatabase(database string, config *gorm.Config) (*gorm.DB, err
DSN: fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", sql.Config.MysqlAdminUser, sql.Config.MysqlAdminPassword, sql.ServerURL, database), DSN: fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", sql.Config.MysqlAdminUser, sql.Config.MysqlAdminPassword, sql.ServerURL, database),
DefaultStringSize: 256, DefaultStringSize: 256,
} }
// TODO: Use sql.Core.Environment.Dial
db, err := gorm.Open(mysql.New(cfg), config) db, err := gorm.Open(mysql.New(cfg), config)
if err != nil { if err != nil {
@ -63,7 +64,7 @@ func (sql SQL) OpenBookkeeping(silent bool) (*gorm.DB, error) {
func (sql SQL) Snapshot(io stream.IOStream, dest io.Writer, database string) error { func (sql SQL) Snapshot(io stream.IOStream, dest io.Writer, database string) error {
io = io.Streams(dest, nil, nil, 0).NonInteractive() io = io.Streams(dest, nil, nil, 0).NonInteractive()
code, err := sql.Stack().Exec(io, "sql", "mysqldump", "--databases", database) code, err := sql.Stack(sql.Environment).Exec(io, "sql", "mysqldump", "--databases", database)
if err != nil { if err != nil {
return err return err
} }
@ -75,7 +76,7 @@ func (sql SQL) Snapshot(io stream.IOStream, dest io.Writer, database string) err
// OpenShell executes a mysql shell command // OpenShell executes a mysql shell command
func (sql SQL) OpenShell(io stream.IOStream, argv ...string) (int, error) { func (sql SQL) OpenShell(io stream.IOStream, argv ...string) (int, error) {
return sql.Stack().Exec(io, "sql", "mysql", argv...) return sql.Stack(sql.Environment).Exec(io, "sql", "mysql", argv...)
} }
// WaitShell waits for the sql database to be reachable via a docker-compose shell // WaitShell waits for the sql database to be reachable via a docker-compose shell

View file

@ -3,10 +3,10 @@ package sql
import ( import (
"context" "context"
"embed" "embed"
"io/fs"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
type SQL struct { type SQL struct {
@ -25,12 +25,12 @@ func (SQL) Name() string {
//go:embed all:sql //go:embed all:sql
var resources embed.FS var resources embed.FS
func (ssh SQL) Stack() component.StackWithResources { func (ssh SQL) Stack(env environment.Environment) component.StackWithResources {
return ssh.ComponentBase.MakeStack(component.StackWithResources{ return ssh.ComponentBase.MakeStack(env, component.StackWithResources{
Resources: resources, Resources: resources,
ContextPath: "sql", ContextPath: "sql",
MakeDirsPerm: fs.ModeDir | fs.ModePerm, MakeDirsPerm: environment.DefaultDirPerm,
MakeDirs: []string{ MakeDirs: []string{
"data", "data",
}, },

View file

@ -4,6 +4,7 @@ import (
"embed" "embed"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
type SSH struct { type SSH struct {
@ -17,8 +18,8 @@ func (SSH) Name() string {
//go:embed all:stack //go:embed all:stack
var resources embed.FS var resources embed.FS
func (ssh SSH) Stack() component.StackWithResources { func (ssh SSH) Stack(env environment.Environment) component.StackWithResources {
return ssh.ComponentBase.MakeStack(component.StackWithResources{ return ssh.ComponentBase.MakeStack(env, component.StackWithResources{
Resources: resources, Resources: resources,
ContextPath: "stack", ContextPath: "stack",
}) })

View file

@ -6,7 +6,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"github.com/FAU-CDI/wisski-distillery/pkg/execx" "github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
@ -18,6 +18,7 @@ import (
type Stack struct { type Stack struct {
Dir string // Directory this Stack is located in Dir string // Directory this Stack is located in
Env environment.Environment
DockerExecutable string // Path to the native docker executable to use DockerExecutable string // Path to the native docker executable to use
} }
@ -101,7 +102,7 @@ func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string
code, err := ds.compose(io, compose...) code, err := ds.compose(io, compose...)
if err != nil { if err != nil {
return execx.ExecCommandError, nil return environment.ExecCommandError, nil
} }
return code, nil return code, nil
} }
@ -175,10 +176,10 @@ func (ds Stack) Down(io stream.IOStream) error {
func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) { func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) {
if ds.DockerExecutable == "" { if ds.DockerExecutable == "" {
var err error var err error
ds.DockerExecutable, err = execx.LookPathAbs("docker") ds.DockerExecutable, err = ds.Env.LookPathAbs("docker")
if err != nil { if err != nil {
return execx.ExecCommandError, err return environment.ExecCommandError, err
} }
} }
return execx.Exec(io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil return ds.Env.Exec(io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil
} }

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"net"
"net/http" "net/http"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
@ -61,6 +62,14 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str
} }
// create the request object // create the request object
client := &http.Client{
Transport: &http.Transport{
Dial: ts.Environment.Dial,
DialTLS: func(network, addr string) (net.Conn, error) {
return nil, errors.New("not implemented")
},
},
}
req, err := http.NewRequest(method, ts.BaseURL+url, reader) req, err := http.NewRequest(method, ts.BaseURL+url, reader)
if err != nil { if err != nil {
return nil, err return nil, err
@ -76,7 +85,7 @@ func (ts Triplestore) OpenRaw(method, url string, body interface{}, bodyName str
req.SetBasicAuth(ts.Config.TriplestoreAdminUser, ts.Config.TriplestoreAdminPassword) req.SetBasicAuth(ts.Config.TriplestoreAdminUser, ts.Config.TriplestoreAdminPassword)
// and send it // and send it
return http.DefaultClient.Do(req) return client.Do(req)
} }
// Wait waits for the connection to the Triplestore to succeed. // Wait waits for the connection to the Triplestore to succeed.

View file

@ -3,11 +3,11 @@ package triplestore
import ( import (
"context" "context"
"embed" "embed"
"io/fs"
"path/filepath" "path/filepath"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
type Triplestore struct { type Triplestore struct {
@ -26,14 +26,13 @@ func (Triplestore) Name() string {
//go:embed all:stack //go:embed all:stack
var resources embed.FS var resources embed.FS
func (ts Triplestore) Stack() component.StackWithResources { func (ts Triplestore) Stack(env environment.Environment) component.StackWithResources {
return ts.ComponentBase.MakeStack(component.StackWithResources{ return ts.ComponentBase.MakeStack(env, component.StackWithResources{
Resources: resources, Resources: resources,
ContextPath: "stack", ContextPath: "stack",
CopyContextFiles: []string{"graphdb.zip"}, // TODO: Move into constant? CopyContextFiles: []string{"graphdb.zip"}, // TODO: Move into constant?
MakeDirsPerm: fs.ModeDir | fs.ModePerm,
MakeDirs: []string{ MakeDirs: []string{
filepath.Join("data", "data"), filepath.Join("data", "data"),
filepath.Join("data", "work"), filepath.Join("data", "work"),

View file

@ -4,6 +4,7 @@ import (
"embed" "embed"
"github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Web implements the ingress gateway for the distillery. // Web implements the ingress gateway for the distillery.
@ -17,11 +18,11 @@ func (Web) Name() string {
return "web" return "web"
} }
func (web Web) Stack() component.StackWithResources { func (web Web) Stack(env environment.Environment) component.StackWithResources {
if web.Config.HTTPSEnabled() { if web.Config.HTTPSEnabled() {
return web.stackHTTPS() return web.stackHTTPS(env)
} else { } else {
return web.stackHTTP() return web.stackHTTP(env)
} }
} }
@ -29,8 +30,8 @@ func (web Web) Stack() component.StackWithResources {
//go:embed web-https.env //go:embed web-https.env
var httpsResources embed.FS var httpsResources embed.FS
func (web Web) stackHTTPS() component.StackWithResources { func (web Web) stackHTTPS(env environment.Environment) component.StackWithResources {
return web.MakeStack(component.StackWithResources{ return web.MakeStack(env, component.StackWithResources{
Resources: httpsResources, Resources: httpsResources,
ContextPath: "web-https", ContextPath: "web-https",
EnvPath: "web-https.env", EnvPath: "web-https.env",
@ -45,8 +46,8 @@ func (web Web) stackHTTPS() component.StackWithResources {
//go:embed web-http.env //go:embed web-http.env
var httpResources embed.FS var httpResources embed.FS
func (web Web) stackHTTP() component.StackWithResources { func (web Web) stackHTTP(env environment.Environment) component.StackWithResources {
return web.MakeStack(component.StackWithResources{ return web.MakeStack(env, component.StackWithResources{
Resources: httpResources, Resources: httpResources,
ContextPath: "web-http", ContextPath: "web-http",
EnvPath: "web-http.env", EnvPath: "web-http.env",

View file

@ -1,10 +1,10 @@
package config package config
import ( import (
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/fsx"
) )
@ -14,19 +14,19 @@ func (cfg Config) ExecutablePath() string {
} }
// UsingDistilleryExecutable checks if the current process is using the distillery executable // UsingDistilleryExecutable checks if the current process is using the distillery executable
func (cfg Config) UsingDistilleryExecutable() bool { func (cfg Config) UsingDistilleryExecutable(env environment.Environment) bool {
exe, err := os.Executable() exe, err := env.Executable()
if err != nil { if err != nil {
return false return false
} }
return fsx.SameFile(exe, cfg.ExecutablePath()) return fsx.SameFile(env, exe, cfg.ExecutablePath())
} }
// CurrentExecutable returns the path to the current executable being used. // CurrentExecutable returns the path to the current executable being used.
// When it does not exist, falls back to the default executable. // When it does not exist, falls back to the default executable.
func (cfg Config) CurrentExecutable() string { func (cfg Config) CurrentExecutable(env environment.Environment) string {
exe, err := os.Executable() exe, err := env.Executable()
if err != nil || !fsx.IsFile(exe) { if err != nil || !fsx.IsFile(env, exe) {
return cfg.ExecutablePath() return cfg.ExecutablePath()
} }
return exe return exe

View file

@ -4,6 +4,7 @@ import (
"io" "io"
"reflect" "reflect"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/envreader" "github.com/FAU-CDI/wisski-distillery/pkg/envreader"
"github.com/FAU-CDI/wisski-distillery/pkg/stringparser" "github.com/FAU-CDI/wisski-distillery/pkg/stringparser"
) )
@ -16,7 +17,7 @@ import (
// When a key is missing, it is set to the default value. // When a key is missing, it is set to the default value.
// //
// See also [stringparser.Parse]. // See also [stringparser.Parse].
func (config *Config) Unmarshal(src io.Reader) error { func (config *Config) Unmarshal(env environment.Environment, src io.Reader) error {
// read all the values! // read all the values!
values, err := envreader.ReadAll(src) values, err := envreader.ReadAll(src)
if err != nil { if err != nil {
@ -32,26 +33,26 @@ func (config *Config) Unmarshal(src io.Reader) error {
tField := tConfig.Field(i) tField := tConfig.Field(i)
vField := vConfig.FieldByName(tField.Name) vField := vConfig.FieldByName(tField.Name)
env := tField.Tag.Get("env") tEnv := tField.Tag.Get("env")
dflt := tField.Tag.Get("default") tDefault := tField.Tag.Get("default")
parser := tField.Tag.Get("parser") tParser := tField.Tag.Get("parser")
// skip it if it isn't loaded! // skip it if it isn't loaded!
if env == "" { if tEnv == "" {
continue continue
} }
// read the value with a default // read the value with a default
value, ok := values[env] value, ok := values[tEnv]
if !ok || value == "" { if !ok || value == "" {
if dflt == "" { if tDefault == "" {
continue continue
} }
value = dflt value = tDefault
} }
// parse the value! // parse the value!
if err := stringparser.Parse(parser, value, vField); err != nil { if err := stringparser.Parse(env, tParser, value, vField); err != nil {
return err return err
} }
} }

View file

@ -7,6 +7,7 @@ import (
"reflect" "reflect"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/hostname" "github.com/FAU-CDI/wisski-distillery/pkg/hostname"
"github.com/FAU-CDI/wisski-distillery/pkg/password" "github.com/FAU-CDI/wisski-distillery/pkg/password"
"github.com/FAU-CDI/wisski-distillery/pkg/unpack" "github.com/FAU-CDI/wisski-distillery/pkg/unpack"
@ -29,13 +30,13 @@ type Template struct {
} }
// SetDefaults sets defaults on the template // SetDefaults sets defaults on the template
func (tpl *Template) SetDefaults() (err error) { func (tpl *Template) SetDefaults(env environment.Environment) (err error) {
if tpl.DeployRoot == "" { if tpl.DeployRoot == "" {
tpl.DeployRoot = core.BaseDirectoryDefault tpl.DeployRoot = core.BaseDirectoryDefault
} }
if tpl.DefaultDomain == "" { if tpl.DefaultDomain == "" {
tpl.DefaultDomain = hostname.FQDN() tpl.DefaultDomain = hostname.FQDN(env)
} }
if tpl.SelfOverridesFile == "" { if tpl.SelfOverridesFile == "" {

View file

@ -3,10 +3,11 @@ package core
import ( import (
"errors" "errors"
"io/fs" "io/fs"
"os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// MetaConfigFile is the path to a configuration file that contains the path to the last used wdcli executable. // MetaConfigFile is the path to a configuration file that contains the path to the last used wdcli executable.
@ -33,7 +34,7 @@ var errReadBaseDirectoryEmpty = errors.New("ReadBaseDirectory: Directory is empt
// Use [ParamsFromEnv] to initialize parameters completely. // Use [ParamsFromEnv] to initialize parameters completely.
// //
// It does not perform any reading of files. // It does not perform any reading of files.
func ReadBaseDirectory() (value string, err error) { func ReadBaseDirectory(env environment.Environment) (value string, err error) {
// get the path! // get the path!
path, err := MetaConfigPath() path, err := MetaConfigPath()
if err != nil { if err != nil {
@ -41,7 +42,7 @@ func ReadBaseDirectory() (value string, err error) {
} }
// read the meta config file! // read the meta config file!
contents, err := os.ReadFile(path) contents, err := environment.ReadFile(env, path)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -59,7 +60,7 @@ func ReadBaseDirectory() (value string, err error) {
} }
// WriteBaseDirectory writes the base directory to the environment, or returns an error // WriteBaseDirectory writes the base directory to the environment, or returns an error
func WriteBaseDirectory(dir string) error { func WriteBaseDirectory(env environment.Environment, dir string) error {
// get the path! // get the path!
path, err := MetaConfigPath() path, err := MetaConfigPath()
if err != nil { if err != nil {
@ -67,5 +68,5 @@ func WriteBaseDirectory(dir string) error {
} }
// just put the directory inside it! // just put the directory inside it!
return os.WriteFile(path, []byte(dir), fs.ModePerm) return environment.WriteFile(env, path, []byte(dir), fs.ModePerm)
} }

View file

@ -1,8 +1,9 @@
package core package core
import ( import (
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Params are used to initialize the excutable. // Params are used to initialize the excutable.
@ -15,9 +16,9 @@ type Params struct {
func ParamsFromEnv() (params Params, err error) { func ParamsFromEnv() (params Params, err error) {
// try to read the base directory! // try to read the base directory!
value, err := ReadBaseDirectory() value, err := ReadBaseDirectory(environment.Native{}) // TODO: Are we sure about the native environment here?
switch { switch {
case os.IsNotExist(err): case environment.IsNotExist(err):
params.ConfigPath = BaseDirectoryDefault params.ConfigPath = BaseDirectoryDefault
case err == nil: case err == nil:
params.ConfigPath = value params.ConfigPath = value

View file

@ -1,7 +1,6 @@
package wisski package wisski
import ( import (
"os"
"path/filepath" "path/filepath"
"time" "time"
@ -18,7 +17,7 @@ func (dis *Distillery) PruneBackups(io stream.IOStream) error {
sPath := dis.SnapshotsArchivePath() sPath := dis.SnapshotsArchivePath()
// list all the files // list all the files
entries, err := os.ReadDir(sPath) entries, err := dis.Core.Environment.ReadDir(sPath)
if err != nil { if err != nil {
return err return err
} }
@ -44,7 +43,7 @@ func (dis *Distillery) PruneBackups(io stream.IOStream) error {
path := filepath.Join(sPath, entry.Name()) path := filepath.Join(sPath, entry.Name())
io.Printf("Removing %s cause it is older than %d days", path, dis.Config.MaxBackupAge) io.Printf("Removing %s cause it is older than %d days", path, dis.Config.MaxBackupAge)
if err := os.Remove(path); err != nil { if err := dis.Core.Environment.Remove(path); err != nil {
return err return err
} }
} }

View file

@ -58,7 +58,7 @@ func makeComponent[C component.Component](dis *Distillery, field *lazy.Lazy[C],
} }
base := c.Base() base := c.Base()
base.Config = dis.Config base.Core = dis.Core
if base.Dir == "" { if base.Dir == "" {
base.Dir = filepath.Join(dis.Config.DeployRoot, "core", c.Name()) base.Dir = filepath.Join(dis.Config.DeployRoot, "core", c.Name())
} }

View file

@ -3,16 +3,15 @@ package wisski
import ( import (
"context" "context"
"github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/component"
) )
// Distillery represents a WissKI Distillery // Distillery represents a WissKI Distillery
// //
// It is the main structure used to interact with different components. // It is the main structure used to interact with different components.
type Distillery struct { type Distillery struct {
// Config holds the configuration of the distillery. // core holds the core of the distillery
// It is read directly from a configuration file. component.Core
Config *config.Config
// Upstream holds information to connect to the various running // Upstream holds information to connect to the various running
// distillery components. // distillery components.
@ -25,7 +24,7 @@ type Distillery struct {
components components
} }
// Upstream are the upstream urls connecting to the various external components. // Upstream contains the configuration for accessing remote configuration.
type Upstream struct { type Upstream struct {
SQL string SQL string
Triplestore string Triplestore string

View file

@ -1,10 +1,10 @@
package wisski package wisski
import ( import (
"os" "github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/tkw1536/goprogram/exit" "github.com/tkw1536/goprogram/exit"
) )
@ -21,6 +21,9 @@ var errOpenConfig = exit.Error{
// NewDistillery creates a new distillery from the provided flags // NewDistillery creates a new distillery from the provided flags
func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) (dis *Distillery, err error) { func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) (dis *Distillery, err error) {
dis = &Distillery{ dis = &Distillery{
Core: component.Core{
Environment: environment.Native{},
},
Upstream: Upstream{ Upstream: Upstream{
SQL: "127.0.0.1:3306", SQL: "127.0.0.1:3306",
Triplestore: "127.0.0.1:7200", Triplestore: "127.0.0.1:7200",
@ -34,7 +37,7 @@ func NewDistillery(params core.Params, flags core.Flags, req core.Requirements)
if flags.InternalInDocker { if flags.InternalInDocker {
dis.Upstream.SQL = "sql:3306" dis.Upstream.SQL = "sql:3306"
dis.Upstream.Triplestore = "triplestore:7200" dis.Upstream.Triplestore = "triplestore:7200"
params.ConfigPath = os.Getenv("CONFIG_PATH") params.ConfigPath = dis.Core.Environment.GetEnv("CONFIG_PATH")
} }
// 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
@ -52,7 +55,7 @@ func NewDistillery(params core.Params, flags core.Flags, req core.Requirements)
} }
// open the config file! // open the config file!
f, err := os.Open(params.ConfigPath) f, err := dis.Core.Environment.Open(params.ConfigPath)
if err != nil { if err != nil {
return nil, errOpenConfig.WithMessageF(err) return nil, errOpenConfig.WithMessageF(err)
} }
@ -62,6 +65,6 @@ func NewDistillery(params core.Params, flags core.Flags, req core.Requirements)
dis.Config = &config.Config{ dis.Config = &config.Config{
ConfigPath: cfg, ConfigPath: cfg,
} }
err = dis.Config.Unmarshal(f) err = dis.Config.Unmarshal(dis.Core.Environment, f)
return return
} }

View file

@ -4,8 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/fs"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -13,6 +11,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/bookkeeping" "github.com/FAU-CDI/wisski-distillery/internal/bookkeeping"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/pkg/countwriter" "github.com/FAU-CDI/wisski-distillery/pkg/countwriter"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/FAU-CDI/wisski-distillery/pkg/opgroup" "github.com/FAU-CDI/wisski-distillery/pkg/opgroup"
@ -42,7 +41,7 @@ func (dis *Distillery) SnapshotsArchivePath() string {
// The path is guaranteed to not exist. // The path is guaranteed to not exist.
func (dis *Distillery) NewSnapshotArchivePath(prefix string) (path string) { func (dis *Distillery) NewSnapshotArchivePath(prefix string) (path string) {
// TODO: Consider moving these into a subdirectory with the provided prefix. // TODO: Consider moving these into a subdirectory with the provided prefix.
for path == "" || fsx.Exists(path) { for path == "" || fsx.Exists(dis.Environment, path) {
name := dis.newSnapshotName(prefix) + ".tar.gz" name := dis.newSnapshotName(prefix) + ".tar.gz"
path = filepath.Join(dis.SnapshotsArchivePath(), name) path = filepath.Join(dis.SnapshotsArchivePath(), name)
} }
@ -64,9 +63,9 @@ func (*Distillery) newSnapshotName(prefix string) string {
// NewSnapshotStagingDir returns the path to a new snapshot directory. // NewSnapshotStagingDir returns the path to a new snapshot directory.
// The directory is guaranteed to have been freshly created. // The directory is guaranteed to have been freshly created.
func (dis *Distillery) NewSnapshotStagingDir(prefix string) (path string, err error) { func (dis *Distillery) NewSnapshotStagingDir(prefix string) (path string, err error) {
for path == "" || os.IsExist(err) { for path == "" || environment.IsExist(err) {
path = filepath.Join(dis.SnapshotsStagingPath(), dis.newSnapshotName(prefix)) path = filepath.Join(dis.SnapshotsStagingPath(), dis.newSnapshotName(prefix))
err = os.Mkdir(path, os.ModeDir) err = dis.Core.Environment.Mkdir(path, environment.DefaultFilePerm)
} }
if err != nil { if err != nil {
path = "" path = ""
@ -210,7 +209,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, dis *Distillery, inst
bkPath := filepath.Join(snapshot.Description.Dest, "bookkeeping.txt") bkPath := filepath.Join(snapshot.Description.Dest, "bookkeeping.txt")
files <- bkPath files <- bkPath
info, err := os.Create(bkPath) info, err := dis.Core.Environment.Create(bkPath, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
@ -227,7 +226,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, dis *Distillery, inst
fsPath := filepath.Join(snapshot.Description.Dest, filepath.Base(instance.FilesystemBase)) fsPath := filepath.Join(snapshot.Description.Dest, filepath.Base(instance.FilesystemBase))
// copy over whatever is in the base directory // copy over whatever is in the base directory
return fsx.CopyDirectory(fsPath, instance.FilesystemBase, func(dst, src string) { return fsx.CopyDirectory(dis.Core.Environment, fsPath, instance.FilesystemBase, func(dst, src string) {
files <- dst files <- dst
}) })
}, &snapshot.ErrFilesystem) }, &snapshot.ErrFilesystem)
@ -237,7 +236,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, dis *Distillery, inst
tsPath := filepath.Join(snapshot.Description.Dest, instance.GraphDBRepository+".nq") tsPath := filepath.Join(snapshot.Description.Dest, instance.GraphDBRepository+".nq")
files <- tsPath files <- tsPath
nquads, err := os.Create(tsPath) nquads, err := dis.Core.Environment.Create(tsPath, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
@ -253,7 +252,7 @@ func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, dis *Distillery, inst
sqlPath := filepath.Join(snapshot.Description.Dest, snapshot.Instance.SqlDatabase+".sql") sqlPath := filepath.Join(snapshot.Description.Dest, snapshot.Instance.SqlDatabase+".sql")
files <- sqlPath files <- sqlPath
sql, err := os.Create(sqlPath) sql, err := dis.Core.Environment.Create(sqlPath, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
@ -279,7 +278,7 @@ func (snapshot *Snapshot) makeWhitebox(io stream.IOStream, dis *Distillery, inst
files <- pbPath files <- pbPath
// create the directory! // create the directory!
if err := os.Mkdir(pbPath, fs.ModeDir); err != nil { if err := dis.Core.Environment.Mkdir(pbPath, environment.DefaultDirPerm); err != nil {
return err return err
} }
@ -312,20 +311,20 @@ func (snapshot *Snapshot) waitGroup(io stream.IOStream, og *opgroup.OpGroup[stri
// WriteReport writes out the report belonging to this snapshot. // WriteReport writes out the report belonging to this snapshot.
// It is a separate function, to allow writing it indepenently of the rest. // It is a separate function, to allow writing it indepenently of the rest.
func (snapshot Snapshot) WriteReport(io stream.IOStream) error { func (snapshot *Snapshot) WriteReport(env environment.Environment, stream stream.IOStream) error {
return logging.LogOperation(func() error { return logging.LogOperation(func() error {
reportPath := filepath.Join(snapshot.Description.Dest, "report.txt") reportPath := filepath.Join(snapshot.Description.Dest, "report.txt")
io.Println(reportPath) stream.Println(reportPath)
// create the report file! // create the report file!
report, err := os.Create(reportPath) report, err := env.Create(reportPath, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
defer report.Close() defer report.Close()
// print the report into it! // print the report into it!
_, err = report.WriteString(snapshot.String()) _, err = io.WriteString(report, snapshot.String())
return err return err
}, io, "Writing snapshot report") }, stream, "Writing snapshot report")
} }

View file

@ -0,0 +1,58 @@
package environment
import (
"io"
"io/fs"
"net"
"time"
"github.com/tkw1536/goprogram/stream"
)
// Environment represents an environment that a program can run it.
// It mostly mimics the interfaces of the [os] package.
type Environment interface {
isEnv()
GetEnv(name string) string
Stat(path string) (fs.FileInfo, error)
Lstat(path string) (fs.FileInfo, error)
Readlink(path string) (string, error)
Symlink(oldname, newname string) error
ReadDir(name string) ([]fs.DirEntry, error)
Open(path string) (fs.File, error)
Chtimes(name string, atime time.Time, mtime time.Time) error
SameFile(f1, f2 fs.FileInfo) bool
Create(path string, mode fs.FileMode) (WritableFile, error)
Mkdir(path string, mode fs.FileMode) error
MkdirAll(path string, mode fs.FileMode) error
Remove(path string) error
RemoveAll(path string) error
WalkDir(root string, fn fs.WalkDirFunc) error
Abs(path string) (string, error)
Listen(network, address string) (net.Listener, error)
Dial(network, address string) (net.Conn, error)
Executable() (string, error)
Exec(io stream.IOStream, workdir string, exe string, argv ...string) int
LookPathAbs(name string) (string, error)
}
type WritableFile interface {
fs.File
io.Writer
}
func init() {
var _ Environment = Native{}
}

View file

@ -0,0 +1,69 @@
package environment
import (
"bytes"
"io"
"io/fs"
"os"
"github.com/tkw1536/goprogram/stream"
)
// ExecCommandError is returned by Exec when a command could not be executed.
// This typically hints that the executable cannot be found, but may have other causes.
const ExecCommandError = 127
// DefaultFilePerm is the default mode to use for files
const DefaultFilePerm fs.FileMode = 0666
// DefaultDirPerm is the default mode to use for directories
const DefaultDirPerm fs.FileMode = os.ModeDir & fs.ModePerm
// IsExist checks if the provided error represents a 'does not exist' errror
func IsExist(err error) bool {
return os.IsExist(err)
}
// IsNotExist checks if the provided error represents a 'does exist' error
func IsNotExist(err error) bool {
return os.IsNotExist(err)
}
// WriteFile is like [os.WriteFile].
func WriteFile(env Environment, path string, data []byte, mode fs.FileMode) error {
handle, err := env.Create(path, mode)
if err != nil {
return err
}
defer handle.Close()
if _, err := handle.Write(data); err != nil {
return err
}
return nil
}
// ReadFile is like [os.ReadFile]
func ReadFile(env Environment, path string) ([]byte, error) {
// open the file!
file, err := env.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// copy everything into a buffer!
var buffer bytes.Buffer
if _, err := io.Copy(&buffer, file); err != nil {
return nil, err
}
// return the buffer contents!
return buffer.Bytes(), nil
}
// MustExec is like Exec, except that it returns true if the command exited successfully, and else false.
func MustExec(env Environment, io stream.IOStream, workdir string, exe string, argv ...string) bool {
return env.Exec(io, workdir, exe, argv...) == 0
}

View file

@ -1,5 +1,4 @@
// Package execx defines extensions to the "os/exec" package package environment
package execx
import ( import (
"os/exec" "os/exec"
@ -7,15 +6,11 @@ import (
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
// ExecCommandError is returned by Exec when a command could not be executed.
// This typically hints that the executable cannot be found, but may have other causes.
const ExecCommandError = 127
// Exec executes a system command with the specified input/output streams, working directory, and arguments. // Exec executes a system command with the specified input/output streams, working directory, and arguments.
// //
// If the command executes, it's exit code will be returned. // If the command executes, it's exit code will be returned.
// If the command can not be executed, returns [ExecCommandError]. // If the command can not be executed, returns [ExecCommandError].
func Exec(io stream.IOStream, workdir string, exe string, argv ...string) int { func (Native) Exec(io stream.IOStream, workdir string, exe string, argv ...string) int {
// setup the command // setup the command
cmd := exec.Command(exe, argv...) cmd := exec.Command(exe, argv...)
cmd.Dir = workdir cmd.Dir = workdir
@ -40,7 +35,10 @@ func Exec(io stream.IOStream, workdir string, exe string, argv ...string) int {
return 0 return 0
} }
// MustExec is like Exec, except that it returns true if the command exited successfully, and else false. func (n Native) LookPathAbs(file string) (string, error) {
func MustExec(io stream.IOStream, workdir string, exe string, argv ...string) bool { path, err := exec.LookPath(file)
return Exec(io, workdir, exe, argv...) == 0 if err != nil {
return "", err
}
return n.Abs(path)
} }

View file

@ -0,0 +1,80 @@
package environment
import (
"io/fs"
"os"
"path/filepath"
"time"
)
type Native struct{}
func (Native) isEnv() {}
func (Native) GetEnv(name string) string {
return os.Getenv(name)
}
func (Native) Stat(path string) (fs.FileInfo, error) {
return os.Stat(path)
}
func (Native) Lstat(path string) (fs.FileInfo, error) {
return os.Lstat(path)
}
func (Native) Readlink(path string) (string, error) {
return os.Readlink(path)
}
func (Native) Symlink(oldname, newname string) error {
return os.Symlink(oldname, newname)
}
func (Native) ReadDir(name string) ([]fs.DirEntry, error) {
return os.ReadDir(name)
}
func (Native) SameFile(f1, f2 fs.FileInfo) bool {
return os.SameFile(f1, f2)
}
func (Native) WalkDir(path string, f fs.WalkDirFunc) error {
return filepath.WalkDir(path, f)
}
func (Native) Executable() (string, error) {
return os.Executable() // TODO: not sure this works with the remote concepts
}
func (Native) Open(path string) (fs.File, error) {
return os.Open(path)
}
func (Native) Create(path string, mode fs.FileMode) (WritableFile, error) {
return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
}
func (Native) Chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
func (Native) Mkdir(path string, mode fs.FileMode) error {
return os.Mkdir(path, mode)
}
func (Native) MkdirAll(path string, mode fs.FileMode) error {
return os.MkdirAll(path, mode)
}
func (Native) Remove(path string) error {
return os.Remove(path)
}
func (Native) RemoveAll(path string) error {
return os.RemoveAll(path)
}
func (Native) Abs(path string) (string, error) {
return filepath.Abs(path)
}

View file

@ -0,0 +1,11 @@
package environment
import "net"
func (Native) Listen(network, address string) (net.Listener, error) {
return net.Listen(network, address)
}
func (Native) Dial(network, address string) (net.Conn, error) {
return net.Dial(network, address)
}

View file

@ -1,15 +0,0 @@
package execx
import (
"os/exec"
"path/filepath"
)
// LookPathAbs is like [exec.LookPath], but always returns an absolute path
func LookPathAbs(file string) (string, error) {
path, err := exec.LookPath(file)
if err != nil {
return "", err
}
return filepath.Abs(path)
}

View file

@ -4,8 +4,9 @@ import (
"errors" "errors"
"io" "io"
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
var ErrCopySameFile = errors.New("src and dst must be different") var ErrCopySameFile = errors.New("src and dst must be different")
@ -14,13 +15,13 @@ var ErrCopySameFile = errors.New("src and dst must be different")
// When src points to a symbolic link, will copy the symbolic link. // When src points to a symbolic link, will copy the symbolic link.
// //
// 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(env environment.Environment, dst, src string) error {
if SameFile(src, dst) { if SameFile(env, src, dst) {
return ErrCopySameFile return ErrCopySameFile
} }
// open the source // open the source
srcFile, err := os.Open(src) srcFile, err := env.Open(src)
if err != nil { if err != nil {
return err return err
} }
@ -33,7 +34,7 @@ func CopyFile(dst, src string) error {
} }
// open or create the destination // open or create the destination
dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, srcStat.Mode()) dstFile, err := env.Create(dst, srcStat.Mode())
if err != nil { if err != nil {
return err return err
} }
@ -46,27 +47,27 @@ func CopyFile(dst, src string) error {
// CopyLink copies a link from src to dst. // CopyLink copies a link from src to dst.
// If dst already exists, it is deleted and then re-created. // If dst already exists, it is deleted and then re-created.
func CopyLink(dst, src string) error { func CopyLink(env environment.Environment, dst, src string) error {
// if they're the same file that is an error // if they're the same file that is an error
if SameFile(dst, src) { if SameFile(env, dst, src) {
return ErrCopySameFile return ErrCopySameFile
} }
// read the link target // read the link target
target, err := os.Readlink(src) target, err := env.Readlink(src)
if err != nil { if err != nil {
return err return err
} }
// delete it if it already exists // delete it if it already exists
if Exists(dst) { if Exists(env, dst) {
if err := os.Remove(dst); err != nil { if err := env.Remove(dst); err != nil {
return err return err
} }
} }
// make the symbolic link! // make the symbolic link!
return os.Symlink(target, dst) return env.Symlink(target, dst)
} }
var ErrDstFile = errors.New("dst is a file") var ErrDstFile = errors.New("dst is a file")
@ -77,16 +78,16 @@ var ErrDstFile = errors.New("dst is a file")
// When a directory already exists, additional files are not deleted. // When a directory already exists, additional files are not deleted.
// //
// onCopy, when not nil, is called for each file or directory being copied. // onCopy, when not nil, is called for each file or directory being copied.
func CopyDirectory(dst, src string, onCopy func(dst, src string)) error { func CopyDirectory(env environment.Environment, dst, src string, onCopy func(dst, src string)) error {
// sanity checks // sanity checks
if SameFile(src, dst) { if SameFile(env, src, dst) {
return ErrCopySameFile return ErrCopySameFile
} }
if IsFile(dst) { if IsFile(env, dst) {
return ErrDstFile return ErrDstFile
} }
return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error { return env.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
return err return err
} }
@ -111,19 +112,19 @@ func CopyDirectory(dst, src string, onCopy func(dst, src string)) error {
} }
// if we have a symbolic link, copy the link! // if we have a symbolic link, copy the link!
if info.Mode()&os.ModeSymlink != 0 { if info.Mode()&fs.ModeSymlink != 0 {
return CopyLink(dst, path) return CopyLink(env, dst, path)
} }
// if we got a file, we should copy it normally // if we got a file, we should copy it normally
if !d.IsDir() { if !d.IsDir() {
return CopyFile(dst, path) return CopyFile(env, dst, path)
} }
// create the directory, but ignore an error if the directory already exists. // create the directory, but ignore an error if the directory already exists.
// this is so that we can copy one tree into another tree. // this is so that we can copy one tree into another tree.
err = os.Mkdir(dst, info.Mode()) err = env.Mkdir(dst, info.Mode())
if os.IsExist(err) && IsDirectory(dst) { if environment.IsExist(err) && IsDirectory(env, dst) {
err = nil err = nil
} }

View file

@ -1,17 +1,18 @@
package fsx package fsx
import ( import (
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// SameFile checks if path1 and path2 refer to the same file. // SameFile checks if path1 and path2 refer to the same file.
// If both files exist, they are compared using [os.SameFile]. // If both files exist, they are compared using [env.SameFile].
// If both files do not exist, the paths are first compared syntactically and then via recursion on [filepath.Dir]. // 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 { func SameFile(env environment.Environment, path1, path2 string) bool {
// initial attempt: check if directly // initial attempt: check if directly
same, certain := couldBeSameFile(path1, path2) same, certain := couldBeSameFile(env, path1, path2)
if certain { if certain {
return same return same
} }
@ -28,28 +29,28 @@ func SameFile(path1, path2 string) bool {
// compare the base names! // compare the base names!
{ {
same, _ := couldBeSameFile(d1, d2) same, _ := couldBeSameFile(env, d1, d2)
return same return same
} }
} }
// couldBeSameFile checks if path1 might be the same as path2. // couldBeSameFile checks if path1 might be the same as path2.
// //
// If both files exist, compares using [os.SameFile]. // If both files exist, compares using [env.SameFile].
// Otherwise compares absolute paths using string comparison. // Otherwise compares absolute paths using string comparison.
// //
// same indicates if they might be the same file. // same indicates if they might be the same file.
// authorative indiciates if the result is authorative. // authorative indiciates if the result is authorative.
func couldBeSameFile(path1, path2 string) (same, authorative bool) { func couldBeSameFile(env environment.Environment, path1, path2 string) (same, authorative bool) {
{ {
// stat both files // stat both files
info1, err1 := os.Stat(path1) info1, err1 := env.Stat(path1)
info2, err2 := os.Stat(path2) info2, err2 := env.Stat(path2)
// both files exist => check using os.SameFile // both files exist => check using env.SameFile
// the result is always authorative // the result is always authorative
if err1 == nil && err2 == nil { if err1 == nil && err2 == nil {
same = os.SameFile(info1, info2) same = env.SameFile(info1, info2)
authorative = true authorative = true
return return
} }
@ -60,15 +61,15 @@ func couldBeSameFile(path1, path2 string) (same, authorative bool) {
} }
// only 1 file does not exist => they could be different // only 1 file does not exist => they could be different
if os.IsNotExist(err1) != os.IsNotExist(err2) { if environment.IsNotExist(err1) != environment.IsNotExist(err2) {
return return
} }
} }
{ {
// resolve paths absolutely // resolve paths absolutely
rpath1, err1 := filepath.Abs(path1) rpath1, err1 := env.Abs(path1)
rpath2, err2 := filepath.Abs(path2) rpath2, err2 := env.Abs(path2)
// if either path could not be resolved absolutely // if either path could not be resolved absolutely
// fallback to just using clean! // fallback to just using clean!

View file

@ -1,20 +1,21 @@
package fsx package fsx
import ( import (
"os"
"time" "time"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Touch touches a file. // Touch touches a file.
// It is similar to the unix 'touch' command. // 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 not exist exists, it is created using [env.Create].
// If the file does exist, it's access and modification times are updated to the current time. // If the file does exist, it's access and modification times are updated to the current time.
func Touch(path string) error { func Touch(env environment.Environment, path string) error {
_, err := os.Stat(path) _, err := env.Stat(path)
switch { switch {
case os.IsNotExist(err): case environment.IsNotExist(err):
f, err := os.Create(path) f, err := env.Create(path, environment.DefaultFilePerm)
if err != nil { if err != nil {
return err return err
} }
@ -24,6 +25,6 @@ func Touch(path string) error {
return err return err
default: default:
now := time.Now().Local() now := time.Now().Local()
return os.Chtimes(path, now, now) return env.Chtimes(path, now, now)
} }
} }

View file

@ -2,29 +2,31 @@
package fsx package fsx
import ( import (
"os" "io/fs"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Exists checks if the given path exists // Exists checks if the given path exists
func Exists(path string) bool { func Exists(env environment.Environment, path string) bool {
_, err := os.Lstat(path) _, err := env.Lstat(path)
return err == nil return err == nil
} }
// IsDirectory checks if the provided path exists and is a directory // IsDirectory checks if the provided path exists and is a directory
func IsDirectory(path string) bool { func IsDirectory(env environment.Environment, path string) bool {
info, err := os.Stat(path) info, err := env.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 // IsFile checks if the provided path exists and is a regular file
func IsFile(path string) bool { func IsFile(env environment.Environment, path string) bool {
info, err := os.Stat(path) info, err := env.Stat(path)
return err == nil && info.Mode().IsRegular() return err == nil && info.Mode().IsRegular()
} }
// IsLink checks if the provided path exists and is a symlink // IsLink checks if the provided path exists and is a symlink
func IsLink(path string) bool { func IsLink(env environment.Environment, path string) bool {
info, err := os.Lstat(path) info, err := env.Lstat(path)
return err == nil && info.Mode()&os.ModeSymlink != 0 return err == nil && info.Mode()&fs.ModeSymlink != 0
} }

View file

@ -4,12 +4,14 @@ package hostname
import ( import (
"os" "os"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/Showmax/go-fqdn" "github.com/Showmax/go-fqdn"
) )
// FQDN attempts to return the fully qualified domain name of the host system. // FQDN attempts to return the fully qualified domain name of the host system.
// If an error occurs, may fall back to the empty string. // If an error occurs, may fall back to the empty string.
func FQDN() string { func FQDN(env environment.Environment) string {
// TODO: Pass this through!
// try the hostname function // try the hostname function
{ {

View file

@ -4,11 +4,12 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Parse parses the provided value with the parser. // Parse parses the provided value with the parser.
func Parse(name, value string, vField reflect.Value) error { func Parse(env environment.Environment, name, value string, vField reflect.Value) error {
// use the validator // use the validator
parser, ok := knownParsers[strings.ToLower(name)] parser, ok := knownParsers[strings.ToLower(name)]
@ -17,7 +18,7 @@ func Parse(name, value string, vField reflect.Value) error {
} }
// get the parsed value // get the parsed value
checked, err := parser(value) checked, err := parser(env, value)
if err != nil { if err != nil {
return errors.Wrapf(err, "parser %s returned error", name) return errors.Wrapf(err, "parser %s returned error", name)
} }
@ -53,8 +54,8 @@ var knownParsers map[string]Parser[any] = map[string]Parser[any]{
} }
func asGenericParser[T any](parser Parser[T]) Parser[any] { func asGenericParser[T any](parser Parser[T]) Parser[any] {
return func(s string) (value any, err error) { return func(env environment.Environment, s string) (value any, err error) {
value, err = parser(s) value, err = parser(env, s)
return return
} }
} }

View file

@ -7,6 +7,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/fsx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -17,19 +18,19 @@ import (
// Parsers can be found in this package as functions called Parse*. // Parsers can be found in this package as functions called Parse*.
// They are refered to by their name, e.g. ParseNonempty can be refered to by the name 'Nonempty'. // They are refered to by their name, e.g. ParseNonempty can be refered to by the name 'Nonempty'.
// See [Parse]. // See [Parse].
type Parser[T any] func(s string) (T, error) type Parser[T any] func(env environment.Environment, s string) (T, error)
// ParseAbspath checks that s is an absolute path and returns it as-is // ParseAbspath checks that s is an absolute path and returns it as-is
func ParseAbspath(s string) (string, error) { func ParseAbspath(env environment.Environment, s string) (string, error) {
if !fsx.IsDirectory(s) { if !fsx.IsDirectory(env, s) {
return "", errors.Errorf("%q does not exist or is not a directory", s) return "", errors.Errorf("%q does not exist or is not a directory", s)
} }
return s, nil return s, nil
} }
// ParseFile checks that s is a valid file and returns it as-is // ParseFile checks that s is a valid file and returns it as-is
func ParseFile(s string) (string, error) { func ParseFile(env environment.Environment, s string) (string, error) {
if !fsx.IsFile(s) { if !fsx.IsFile(env, s) {
return "", errors.Errorf("%q does not exist or is not a regular file", s) return "", errors.Errorf("%q does not exist or is not a regular file", s)
} }
return s, nil return s, nil
@ -38,7 +39,7 @@ func ParseFile(s string) (string, error) {
var errEmptyString = errors.New("value is empty") var errEmptyString = errors.New("value is empty")
// ParseNonEmpty checks that s is a non-empty string and returns it as-is // ParseNonEmpty checks that s is a non-empty string and returns it as-is
func ParseNonEmpty(s string) (string, error) { func ParseNonEmpty(env environment.Environment, s string) (string, error) {
if s == "" { if s == "" {
return "", errEmptyString return "", errEmptyString
} }
@ -48,7 +49,7 @@ func ParseNonEmpty(s string) (string, error) {
var regexpDomain = regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! var regexpDomain = regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer!
// ParseValidDomain checks that s is a valid domain and returns it in lowercase // ParseValidDomain checks that s is a valid domain and returns it in lowercase
func ParseValidDomain(s string) (string, error) { func ParseValidDomain(env environment.Environment, s string) (string, error) {
if !regexpDomain.MatchString(s) { if !regexpDomain.MatchString(s) {
return "", errors.Errorf("%q is not a valid domain", s) return "", errors.Errorf("%q is not a valid domain", s)
} }
@ -56,7 +57,7 @@ func ParseValidDomain(s string) (string, error) {
} }
// ParseValidDomains checks that s is a comma-seperated list of valid domains and returns them in lower case // ParseValidDomains checks that s is a comma-seperated list of valid domains and returns them in lower case
func ParseValidDomains(s string) ([]string, error) { func ParseValidDomains(env environment.Environment, s string) ([]string, error) {
if len(s) == 0 { if len(s) == 0 {
return []string{}, nil return []string{}, nil
} }
@ -70,13 +71,13 @@ func ParseValidDomains(s string) ([]string, error) {
} }
// ParseNumber parses s as a decimal integer // ParseNumber parses s as a decimal integer
func ParseNumber(s string) (int, error) { func ParseNumber(env environment.Environment, s string) (int, error) {
value, err := strconv.ParseInt(s, 10, 64) value, err := strconv.ParseInt(s, 10, 64)
return int(value), err return int(value), err
} }
// ParseHttpsURL parses a string into a url that starts with 'https://' // ParseHttpsURL parses a string into a url that starts with 'https://'
func ParseHttpsURL(s string) (*url.URL, error) { func ParseHttpsURL(env environment.Environment, s string) (*url.URL, error) {
url, err := url.Parse(s) url, err := url.Parse(s)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "%q is not a valid URL", s) return nil, errors.Wrapf(err, "%q is not a valid URL", s)
@ -90,7 +91,7 @@ func ParseHttpsURL(s string) (*url.URL, error) {
var regexpEmail = regexp.MustCompile(`^([-a-zA-Z0-9]+)\@([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! var regexpEmail = regexp.MustCompile(`^([-a-zA-Z0-9]+)\@([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer!
// ParseEmail checks that s represents an email, and then returns it as is. // ParseEmail checks that s represents an email, and then returns it as is.
func ParseEmail(s string) (string, error) { func ParseEmail(env environment.Environment, s string) (string, error) {
if s == "" { // no email provided if s == "" { // no email provided
return "", nil return "", nil
} }
@ -103,7 +104,7 @@ func ParseEmail(s string) (string, error) {
var regexpSlug = regexp.MustCompile(`^[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! var regexpSlug = regexp.MustCompile(`^[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer!
// ParseSlug parses s as a slug and returns it as is. // ParseSlug parses s as a slug and returns it as is.
func ParseSlug(s string) (string, error) { func ParseSlug(env environment.Environment, s string) (string, error) {
if !regexpSlug.MatchString(s) { if !regexpSlug.MatchString(s) {
return "", errors.Errorf("%q is not a valid slug", s) return "", errors.Errorf("%q is not a valid slug", s)
} }

View file

@ -6,17 +6,18 @@ import (
"compress/gzip" "compress/gzip"
"io" "io"
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
// Package packages the source directory into a 'tar.gz' file into destination. // Package packages the source directory into a 'tar.gz' file into destination.
// If the destination already exists, it is truncated. // If the destination already exists, it is truncated.
// //
// onCopy, when not nil, is called for each file being copied into the archive. // onCopy, when not nil, is called for each file being copied into the archive.
func Package(dst, src string, onCopy func(rel string, src string)) (count int64, err error) { func Package(env environment.Environment, dst, src string, onCopy func(rel string, src string)) (count int64, err error) {
// create the target archive // create the target archive
archive, err := os.Create(dst) archive, err := env.Create(dst, environment.DefaultFilePerm)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -31,7 +32,7 @@ func Package(dst, src string, onCopy func(rel string, src string)) (count int64,
defer tarHandle.Close() defer tarHandle.Close()
// and walk through it! // and walk through it!
err = filepath.WalkDir(src, func(path string, entry fs.DirEntry, err error) error { err = env.WalkDir(src, func(path string, entry fs.DirEntry, err error) error {
if err != nil { if err != nil {
return err return err
} }
@ -74,7 +75,7 @@ func Package(dst, src string, onCopy func(rel string, src string)) (count int64,
} }
// open the file // open the file
handle, err := os.Open(path) handle, err := env.Open(path)
if err != nil { if err != nil {
return err return err
} }

View file

@ -3,9 +3,9 @@ package unpack
import ( import (
"io" "io"
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -16,9 +16,9 @@ var errExpectedDirectoryButGotFile = errors.New("expected a directory, but got a
// //
// onInstallFile is called for each file or directory being installed. // onInstallFile is called for each file or directory being installed.
// //
// If the destination path does not exist, it is created using [os.MakeDirs] // If the destination path does not exist, it is created using [environment.MakeDirs]
// The directory is installed recursively. // The directory is installed recursively.
func InstallDir(dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error { func InstallDir(env environment.Environment, dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
// open the source file // open the source file
srcFile, err := fsys.Open(src) srcFile, err := fsys.Open(src)
if err != nil { if err != nil {
@ -43,14 +43,14 @@ func InstallDir(dst string, src string, fsys fs.FS, onInstallFile func(dst, src
// do the installation of the directory. // do the installation of the directory.
// the type cast should be safe. // the type cast should be safe.
return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile) return installDir(env, dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile)
} }
// installResource installs the resource at src within fsys to dst. // installResource installs the resource at src within fsys to dst.
// //
// OnInstallFile is called for each source and destination file. // OnInstallFile is called for each source and destination file.
// OnInstallFile may be nil. // OnInstallFile may be nil.
func installResource(dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error { func installResource(env environment.Environment, dst string, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
// open the srcFile // open the srcFile
srcFile, err := fsys.Open(src) srcFile, err := fsys.Open(src)
if err != nil { if err != nil {
@ -71,19 +71,19 @@ func installResource(dst string, src string, fsys fs.FS, onInstallFile func(dst,
// this is a directory, so the cast is safe! // this is a directory, so the cast is safe!
if srcInfo.IsDir() { if srcInfo.IsDir() {
return installDir(dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile) return installDir(env, dst, srcInfo, srcFile.(fs.ReadDirFile), src, fsys, onInstallFile)
} }
// this is a regular file! // this is a regular file!
return installFile(dst, srcInfo, srcFile) return installFile(env, dst, srcInfo, srcFile)
} }
func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src string, fsys fs.FS, onInstallFile func(dst, src string)) error { func installDir(env environment.Environment, dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src string, fsys fs.FS, onInstallFile func(dst, src string)) error {
// create the destination // create the destination
dstStat, dstErr := os.Stat(dst) dstStat, dstErr := env.Stat(dst)
switch { switch {
case os.IsNotExist(dstErr): case environment.IsNotExist(dstErr):
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil { if err := env.MkdirAll(dst, srcInfo.Mode()); err != nil {
return errors.Wrapf(err, "Error creating destination directory %s", dst) return errors.Wrapf(err, "Error creating destination directory %s", dst)
} }
case dstErr != nil: case dstErr != nil:
@ -105,6 +105,7 @@ func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src str
// iterate over all the children // iterate over all the children
for _, entry := range entries { for _, entry := range entries {
if err := installResource( if err := installResource(
env,
filepath.Join(dst, entry.Name()), filepath.Join(dst, entry.Name()),
filepath.Join(src, entry.Name()), filepath.Join(src, entry.Name()),
fsys, fsys,
@ -117,9 +118,9 @@ func installDir(dst string, srcInfo fs.FileInfo, srcFile fs.ReadDirFile, src str
return nil return nil
} }
func installFile(dst string, srcInfo fs.FileInfo, src fs.File) error { func installFile(env environment.Environment, dst string, srcInfo fs.FileInfo, src fs.File) error {
// create the file using the right mode! // create the file using the right mode!
file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) file, err := env.Create(dst, srcInfo.Mode())
if err != nil { if err != nil {
return err return err
} }

View file

@ -6,9 +6,9 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"os"
"strings" "strings"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -201,7 +201,7 @@ parseloop:
// Any existing file is truncated and overwritten. // Any existing file is truncated and overwritten.
// //
// See [WriteTemplate] for possible errors. // See [WriteTemplate] for possible errors.
func InstallTemplate(dst string, context map[string]string, src string, fsys fs.FS) error { func InstallTemplate(env environment.Environment, dst string, context map[string]string, src string, fsys fs.FS) error {
// open the srcFile // open the srcFile
srcFile, err := fsys.Open(src) srcFile, err := fsys.Open(src)
@ -222,7 +222,7 @@ func InstallTemplate(dst string, context map[string]string, src string, fsys fs.
} }
// open the destination file // open the destination file
file, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode()) file, err := env.Create(dst, srcInfo.Mode())
if err != nil { if err != nil {
return err return err
} }

View file

@ -41,7 +41,7 @@ func NewProgram() Program {
// when not running inside docker and we need a distillery // when not running inside docker and we need a distillery
// then we should warn if we are not using the distillery executable. // then we should warn if we are not using the distillery executable.
if dis := context.Environment; !context.Args.Flags.InternalInDocker && context.Description.Requirements.NeedsDistillery && !dis.Config.UsingDistilleryExecutable() { if dis := context.Environment; !context.Args.Flags.InternalInDocker && context.Description.Requirements.NeedsDistillery && !dis.Config.UsingDistilleryExecutable(dis.Environment) {
context.EPrintf(warnNoDeployWdcli, core.Executable, dis.Config.ExecutablePath()) context.EPrintf(warnNoDeployWdcli, core.Executable, dis.Config.ExecutablePath())
} }