This commit starts the migration to remove the environment package. It introduced an abstraction that is not being used, and removing it makes the code simpler to maintain. This commit removes all 'exec' related package.
206 lines
6.3 KiB
Go
206 lines
6.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
|
|
"github.com/FAU-CDI/wisski-distillery/internal/cli"
|
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
|
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
|
|
"github.com/FAU-CDI/wisski-distillery/pkg/execx"
|
|
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
|
|
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
|
|
"github.com/tkw1536/goprogram/exit"
|
|
"github.com/tkw1536/goprogram/parser"
|
|
"github.com/tkw1536/goprogram/status"
|
|
)
|
|
|
|
// SystemUpdate is the 'system_update' command
|
|
var SystemUpdate wisski_distillery.Command = systemupdate{}
|
|
|
|
type systemupdate struct {
|
|
InstallDocker bool "short:\"a\" long:\"install-docker\" description:\"try to automatically install docker. assumes `apt-get` as a package manager\""
|
|
Positionals struct {
|
|
GraphdbZip string `positional-arg-name:"PATH_TO_GRAPHDB_ZIP" required:"1-1" description:"path to the graphdb.zip file"`
|
|
} `positional-args:"true"`
|
|
}
|
|
|
|
func (systemupdate) Description() wisski_distillery.Description {
|
|
return wisski_distillery.Description{
|
|
Requirements: cli.Requirements{
|
|
NeedsDistillery: true,
|
|
},
|
|
ParserConfig: parser.Config{
|
|
IncludeUnknown: true,
|
|
},
|
|
Command: "system_update",
|
|
Description: "installs and updates components of the distillery system",
|
|
}
|
|
}
|
|
|
|
var errNoGraphDBZip = exit.Error{
|
|
Message: "%q does not exist",
|
|
ExitCode: exit.ExitCommandArguments,
|
|
}
|
|
|
|
func (s systemupdate) AfterParse() error {
|
|
// TODO: Use a generic environment here!
|
|
if !fsx.IsFile(new(environment.Native), s.Positionals.GraphdbZip) {
|
|
return errNoGraphDBZip.WithMessageF(s.Positionals.GraphdbZip)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var errBoostrapFailedToCreateDirectory = exit.Error{
|
|
Message: "failed to create directory %s: %s",
|
|
ExitCode: exit.ExitGeneric,
|
|
}
|
|
|
|
var errBootstrapComponent = exit.Error{
|
|
Message: "unable to bootstrap %s: %s",
|
|
ExitCode: exit.ExitGeneric,
|
|
}
|
|
|
|
func (si systemupdate) Run(context wisski_distillery.Context) error {
|
|
dis := context.Environment
|
|
|
|
// create all the other directories
|
|
logging.LogMessage(context.Stderr, context.Context, "Ensuring distillery installation directories exist")
|
|
for _, d := range []string{
|
|
dis.Config.Paths.Root,
|
|
dis.Instances().Path(),
|
|
dis.Exporter().StagingPath(),
|
|
dis.Exporter().ArchivePath(),
|
|
dis.Templating().CustomAssetsPath(),
|
|
} {
|
|
context.Println(d)
|
|
if err := dis.Still.Environment.MkdirAll(d, environment.DefaultDirPerm); err != nil {
|
|
return errBoostrapFailedToCreateDirectory.WithMessageF(d, err)
|
|
}
|
|
}
|
|
|
|
if si.InstallDocker {
|
|
// install system updates
|
|
logging.LogMessage(context.Stderr, context.Context, "Updating Operating System Packages")
|
|
if err := si.mustExec(context, "", "apt-get", "update"); err != nil {
|
|
return err
|
|
}
|
|
if err := si.mustExec(context, "", "apt-get", "upgrade", "-y"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// install docker
|
|
logging.LogMessage(context.Stderr, context.Context, "Installing / Updating Docker")
|
|
if err := si.mustExec(context, "", "apt-get", "install", "curl"); err != nil {
|
|
return err
|
|
}
|
|
// TODO: Download directly
|
|
if err := si.mustExec(context, "", "/bin/sh", "-c", "curl -fsSL https://get.docker.com -o - | /bin/sh"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
logging.LogMessage(context.Stderr, context.Context, "Checking that 'docker' is installed")
|
|
if err := si.mustExec(context, "", "docker", "--version"); err != nil {
|
|
return err
|
|
}
|
|
|
|
logging.LogMessage(context.Stderr, context.Context, "Checking that 'docker compose' is available")
|
|
if err := si.mustExec(context, "", "docker", "compose", "version"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// create the docker network
|
|
// TODO: Use docker API for this
|
|
logging.LogMessage(context.Stderr, context.Context, "Updating Docker Configuration")
|
|
si.mustExec(context, "", "docker", "network", "create", dis.Config.Docker.Network)
|
|
|
|
// install and update the various stacks!
|
|
ctx := component.InstallationContext{
|
|
"graphdb.zip": si.Positionals.GraphdbZip,
|
|
}
|
|
|
|
var updated = make(map[string]struct{})
|
|
var updateMutex sync.Mutex
|
|
|
|
if err := logging.LogOperation(func() error {
|
|
return status.RunErrorGroup(context.Stderr, status.Group[component.Installable, error]{
|
|
PrefixString: func(item component.Installable, index int) string {
|
|
return fmt.Sprintf("[update %q]: ", item.Name())
|
|
},
|
|
PrefixAlign: true,
|
|
|
|
Handler: func(item component.Installable, index int, writer io.Writer) error {
|
|
stack := item.Stack(context.Environment.Environment)
|
|
|
|
if err := stack.Install(context.Context, writer, item.Context(ctx)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := stack.Update(context.Context, writer, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
ud, ok := item.(component.Updatable)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
defer func() {
|
|
updateMutex.Lock()
|
|
defer updateMutex.Unlock()
|
|
updated[item.ID()] = struct{}{}
|
|
}()
|
|
|
|
return ud.Update(context.Context, writer)
|
|
},
|
|
}, dis.Installable())
|
|
}, context.Stderr, context.Context, "Performing Stack Updates"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := logging.LogOperation(func() error {
|
|
for _, item := range dis.Updatable() {
|
|
name := item.Name()
|
|
if err := logging.LogOperation(func() error {
|
|
_, ok := updated[item.ID()]
|
|
if ok {
|
|
context.Println("Already updated")
|
|
return nil
|
|
}
|
|
return item.Update(context.Context, context.Stderr)
|
|
}, context.Stderr, context.Context, "Updating Component: %s", name); err != nil {
|
|
return errBootstrapComponent.WithMessageF(name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}, context.Stderr, context.Context, "Performing Component Updates"); err != nil {
|
|
return err
|
|
}
|
|
// TODO: Register cronjob in /etc/cron.d!
|
|
|
|
logging.LogMessage(context.Stderr, context.Context, "System has been updated")
|
|
return nil
|
|
}
|
|
|
|
var errMustExecFailed = exit.Error{
|
|
Message: "process exited with code %d",
|
|
}
|
|
|
|
// mustExec indicates that the given executable process must complete successfully.
|
|
// If it does not, returns errMustExecFailed
|
|
func (si systemupdate) mustExec(context wisski_distillery.Context, workdir string, exe string, argv ...string) error {
|
|
dis := context.Environment
|
|
if workdir == "" {
|
|
workdir = dis.Config.Paths.Root
|
|
}
|
|
code := execx.Exec(context.Context, context.IOStream, workdir, exe, argv...)()
|
|
if code != 0 {
|
|
err := errMustExecFailed.WithMessageF(code)
|
|
err.ExitCode = exit.ExitCode(code)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|