From 5d906169f44fbb599d90d2ba1e42b740dc476dbf Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Fri, 2 Sep 2022 17:52:06 +0200 Subject: [PATCH] internal/stack: Cleanup API This commit cleans up the internal stack API to prepare it for an eventual move to using a native docker client. --- cmd/blind_update.go | 6 ++++- cmd/cron.go | 5 +++- cmd/make_mysql_account.go | 5 +++- cmd/mysql.go | 5 +++- cmd/shell.go | 10 ++++++- cmd/system_update.go | 4 +-- env/instances.go | 17 +++++++----- env/stack.go | 8 +++--- env/stack_resolver.go | 6 +---- env/stack_self.go | 6 +---- env/stack_sql.go | 14 +++++----- env/stack_ssh.go | 6 +---- env/stack_triplestore.go | 6 +---- env/stack_web.go | 6 +---- internal/stack/stack.go | 57 +++++++++++++++++++++++++++++---------- 15 files changed, 96 insertions(+), 65 deletions(-) diff --git a/cmd/blind_update.go b/cmd/blind_update.go index c62dc64..a72734b 100644 --- a/cmd/blind_update.go +++ b/cmd/blind_update.go @@ -3,6 +3,7 @@ package cmd import ( wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/env" + "github.com/FAU-CDI/wisski-distillery/internal/execx" "github.com/tkw1536/goprogram/exit" ) @@ -44,7 +45,10 @@ func (bu blindUpdate) Run(context wisski_distillery.Context) error { } context.EPrintf("Updating instance %s\n", instance.Slug) - code := instance.Shell(context.IOStream, "/utils/blind_update.sh") + code, err := instance.Shell(context.IOStream, "/utils/blind_update.sh") + if err != nil { + return errBlindUpdateFailed.WithMessageF(instance.Slug, execx.ExecCommandError) + } if code != 0 { return errBlindUpdateFailed.WithMessageF(instance.Slug, code) } diff --git a/cmd/cron.go b/cmd/cron.go index 33cc6d8..4d142bb 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -40,7 +40,10 @@ func (cr cron) Run(context wisski_distillery.Context) error { // iterate over the instances and store the last value of error for _, instance := range instances { logging.LogOperation(func() error { - code := instance.Shell(context.IOStream, "/utils/cron.sh") + code, err := instance.Shell(context.IOStream, "/utils/cron.sh") + if err != nil { + context.EPrintln(err) + } if code != 0 { // keep going, because we want to run as many crons as possible err = errBlindUpdateFailed.WithMessageF(instance.Slug, code) diff --git a/cmd/make_mysql_account.go b/cmd/make_mysql_account.go index bacf6c5..5aebf5c 100644 --- a/cmd/make_mysql_account.go +++ b/cmd/make_mysql_account.go @@ -55,7 +55,10 @@ func (mma makeMysqlAccount) Run(context wisski_distillery.Context) error { if err != nil { return err } - code := context.Environment.SQLShell(context.IOStream, "-e", query) + code, err := context.Environment.SQLShell(context.IOStream, "-e", query) + if err != nil { + return err + } if code != 0 { return exit.Error{ diff --git a/cmd/mysql.go b/cmd/mysql.go index 1b9ed29..2c29dda 100644 --- a/cmd/mysql.go +++ b/cmd/mysql.go @@ -32,7 +32,10 @@ func (mysql) Description() wisski_distillery.Description { } func (ms mysql) Run(context wisski_distillery.Context) error { - code := context.Environment.SQLShell(context.IOStream, ms.Positionals.Args...) + code, err := context.Environment.SQLShell(context.IOStream, ms.Positionals.Args...) + if err != nil { + return err + } if code != 0 { return exit.Error{ ExitCode: exit.ExitCode(uint8(code)), diff --git a/cmd/shell.go b/cmd/shell.go index ebd67fb..98b3391 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -32,13 +32,21 @@ func (shell) Description() wisski_distillery.Description { } } +var errShell = exit.Error{ + Message: "Unable to start shell: %s", + ExitCode: exit.ExitGeneric, +} + func (sh shell) Run(context wisski_distillery.Context) error { instance, err := context.Environment.Instance(sh.Positionals.Slug) if err != nil { return err } - code := instance.Shell(context.IOStream, sh.Positionals.Args...) + code, err := instance.Shell(context.IOStream, sh.Positionals.Args...) + if err != nil { + return errShell.WithMessageF(err) + } if code != 0 { return exit.Error{ ExitCode: exit.ExitCode(uint8(code)), diff --git a/cmd/system_update.go b/cmd/system_update.go index 8b65bbe..f6a7101 100644 --- a/cmd/system_update.go +++ b/cmd/system_update.go @@ -114,13 +114,13 @@ func (si systemupdate) Run(context wisski_distillery.Context) error { for _, stack := range dis.Stacks() { if err := logging.LogOperation(func() error { return stack.Install(context.IOStream, ctx) - }, context.IOStream, "Installing docker stack %q", stack.Name); err != nil { + }, context.IOStream, "Installing docker stack %q", stack.Dir); err != nil { return err } if err := logging.LogOperation(func() error { return stack.Update(context.IOStream, true) - }, context.IOStream, "Updating docker stack %q", stack.Name); err != nil { + }, context.IOStream, "Updating docker stack %q", stack.Dir); err != nil { return err } } diff --git a/env/instances.go b/env/instances.go index b87e406..bcd4c3b 100644 --- a/env/instances.go +++ b/env/instances.go @@ -179,7 +179,7 @@ func (instance *Instance) Delete() error { } // Shell executes a shell command inside the -func (instance Instance) Shell(io stream.IOStream, argv ...string) int { +func (instance Instance) Shell(io stream.IOStream, argv ...string) (int, error) { return instance.Stack().Exec(io, "barrel", "/user_shell.sh", argv...) } @@ -219,8 +219,7 @@ func (instance Instance) URL() *url.URL { func (instance Instance) Stack() stack.Installable { return stack.Installable{ Stack: stack.Stack{ - Name: "barrel", - Dir: instance.FilesystemBase, + Dir: instance.FilesystemBase, }, ContextResource: filepath.Join("resources", "compose", "barrel"), @@ -252,8 +251,7 @@ func (instance Instance) Stack() stack.Installable { func (instance Instance) ReserveStack() stack.Installable { return stack.Installable{ Stack: stack.Stack{ - Name: "reserve", - Dir: instance.FilesystemBase, + Dir: instance.FilesystemBase, }, ContextResource: filepath.Join("resources", "compose", "reserve"), @@ -308,7 +306,11 @@ func (instance Instance) Provision(io stream.IOStream) error { // TODO: Move the provision script into the control plane! provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ") - if st.Run(io, true, "barrel", "/bin/bash", "-c", provisionScript) != 0 { + code, err := st.Run(io, true, "barrel", "/bin/bash", "-c", provisionScript) + if err != nil { + return err + } + if code != 0 { return errors.New("Unable to run provision script") } @@ -336,7 +338,8 @@ func (instance *Instance) PrefixConfig() (config string, err error) { // default prefixes wu := stream.NewIOStream(&builder, nil, nil, 0) - if instance.Stack().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/list_uri_prefixes.php") != 0 { + code, err := instance.Stack().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/list_uri_prefixes.php") + if err != nil || code != 0 { return "", errPrefixExecFailed } diff --git a/env/stack.go b/env/stack.go index 239ea20..2a71ff1 100644 --- a/env/stack.go +++ b/env/stack.go @@ -20,11 +20,11 @@ func (dis *Distillery) Stacks() []stack.Installable { } // asCoreStack treats the provided stack as a core component of this distillery. -func (dis *Distillery) asCoreStack(stack stack.Installable) stack.Installable { - stack.Dir = filepath.Join(dis.Config.DeployRoot, "core", stack.Name) +func (dis *Distillery) asCoreStack(name string, stack stack.Installable) stack.Installable { + stack.Dir = filepath.Join(dis.Config.DeployRoot, "core", name) - stack.ContextResource = filepath.Join("resources", "compose", stack.Name) - stack.EnvFileResource = filepath.Join("resources", "templates", "docker-env", stack.Name) + stack.ContextResource = filepath.Join("resources", "compose", name) + stack.EnvFileResource = filepath.Join("resources", "templates", "docker-env", name) return stack } diff --git a/env/stack_resolver.go b/env/stack_resolver.go index 116f75f..c6f17c8 100644 --- a/env/stack_resolver.go +++ b/env/stack_resolver.go @@ -10,11 +10,7 @@ import ( const ResolverPrefixFile = "prefix.cfg" func (dis *Distillery) ResolverStack() stack.Installable { - stack := dis.asCoreStack(stack.Installable{ - Stack: stack.Stack{ - Name: "resolver", - }, - + stack := dis.asCoreStack("resolver", stack.Installable{ EnvFileContext: map[string]string{ "VIRTUAL_HOST": dis.DefaultVirtualHost(), "LETSENCRYPT_HOST": dis.DefaultLetsencryptHost(), diff --git a/env/stack_self.go b/env/stack_self.go index 241ecad..25e463e 100644 --- a/env/stack_self.go +++ b/env/stack_self.go @@ -8,11 +8,7 @@ func (dis *Distillery) SelfStack() stack.Installable { TARGET = dis.Config.SelfRedirect.String() } - return dis.asCoreStack(stack.Installable{ - Stack: stack.Stack{ - Name: "self", - }, - + return dis.asCoreStack("self", stack.Installable{ EnvFileContext: map[string]string{ "VIRTUAL_HOST": dis.DefaultVirtualHost(), "LETSENCRYPT_HOST": dis.DefaultLetsencryptHost(), diff --git a/env/stack_sql.go b/env/stack_sql.go index 5e2b459..7c1b0a6 100644 --- a/env/stack_sql.go +++ b/env/stack_sql.go @@ -20,11 +20,7 @@ import ( // SQLStack returns the docker stack that handles the sql database. func (dis *Distillery) SQLStack() stack.Installable { - return dis.asCoreStack(stack.Installable{ - Stack: stack.Stack{ - Name: "sql", - }, - + return dis.asCoreStack("sql", stack.Installable{ MakeDirsPerm: fs.ModeDir | fs.ModePerm, MakeDirs: []string{ "data", @@ -87,7 +83,7 @@ func (dis *Distillery) sqlBkTable(silent bool) (*gorm.DB, error) { } // SQLShell executes a mysql shell inside the SQLStack. -func (dis *Distillery) SQLShell(io stream.IOStream, argv ...string) int { +func (dis *Distillery) SQLShell(io stream.IOStream, argv ...string) (int, error) { return dis.SQLStack().Exec(io, "sql", "mysql", argv...) } @@ -102,7 +98,8 @@ const waitSQLInterval = 1 * time.Second func (dis *Distillery) SQLWaitForShell() error { n := stream.FromNil() return wait.Wait(func() bool { - return dis.SQLShell(n, "-e", "show databases;") == 0 + code, err := dis.SQLShell(n, "-e", "show databases;") + return err == nil && code == 0 }, waitSQLInterval, dis.Context()) } @@ -118,7 +115,8 @@ var errInvalidDatabaseName = errors.New("SQLProvision: Invalid database name") func (dis *Distillery) sqlRaw(query string, args ...interface{}) bool { sql := sqle.Format(query, args...) - return dis.SQLShell(stream.FromNil(), "-e", sql) == 0 + code, err := dis.SQLShell(stream.FromNil(), "-e", sql) + return err == nil && code == 0 } // SQLProvision provisions a new sql database and user diff --git a/env/stack_ssh.go b/env/stack_ssh.go index 965b870..385637b 100644 --- a/env/stack_ssh.go +++ b/env/stack_ssh.go @@ -4,11 +4,7 @@ import "github.com/FAU-CDI/wisski-distillery/internal/stack" func (dis *Distillery) SSHStack() stack.Installable { // TODO: Ensure that .env is copied if needed - return dis.asCoreStack(stack.Installable{ - Stack: stack.Stack{ - Name: "sql", - }, - }) + return dis.asCoreStack("ssh", stack.Installable{}) } func (dis *Distillery) SSHStackPath() string { diff --git a/env/stack_triplestore.go b/env/stack_triplestore.go index d7cb436..d8027c9 100644 --- a/env/stack_triplestore.go +++ b/env/stack_triplestore.go @@ -20,11 +20,7 @@ import ( ) func (dis *Distillery) TriplestoreStack() stack.Installable { - return dis.asCoreStack(stack.Installable{ - Stack: stack.Stack{ - Name: "triplestore", - }, - + return dis.asCoreStack("triplestore", stack.Installable{ CopyContextFiles: []string{"graphdb.zip"}, MakeDirsPerm: fs.ModeDir | fs.ModePerm, diff --git a/env/stack_web.go b/env/stack_web.go index ab75427..0f1b5a4 100644 --- a/env/stack_web.go +++ b/env/stack_web.go @@ -3,11 +3,7 @@ package env import "github.com/FAU-CDI/wisski-distillery/internal/stack" func (dis *Distillery) WebStack() stack.Installable { - return dis.asCoreStack(stack.Installable{ - Stack: stack.Stack{ - Name: "web", - }, - + return dis.asCoreStack("web", stack.Installable{ EnvFileContext: map[string]string{ "DEFAULT_HOST": dis.Config.DefaultDomain, }, diff --git a/internal/stack/stack.go b/internal/stack/stack.go index dedc245..81a9f84 100644 --- a/internal/stack/stack.go +++ b/internal/stack/stack.go @@ -14,8 +14,7 @@ import ( // This executable must be capable of the 'docker compose' command. // In the future the idea is to replace this with a native docker compose client. type Stack struct { - Name string // Name of this stack, TODO: Do we need this? - Dir string // Directory of this stack + Dir string // Directory this Stack is located in } var errStackUpdatePull = errors.New("Stack.Update: Pull returned non-zero exit code") @@ -26,11 +25,24 @@ var errStackUpdateBuild = errors.New("Stack.Update: Build returned non-zero exit // // See also Up. func (ds Stack) Update(io stream.IOStream, start bool) error { - if ds.compose(io, "pull") != 0 { - return errStackUpdatePull + { + code, err := ds.compose(io, "pull") + if err != nil { + return err + } + if code != 0 { + return errStackUpdatePull + } } - if ds.compose(io, "build", "--pull") != 0 { - return errStackUpdateBuild + + { + code, err := ds.compose(io, "build", "--pull") + if err != nil { + return err + } + if code != 0 { + return errStackUpdateBuild + } } if start { return ds.Up(io) @@ -43,7 +55,11 @@ var errStackUp = errors.New("Stack.Up: Up returned non-zero exit code") // Up creates and starts the containers in this Stack. // It is equivalent to 'docker compose up -d' on the shell. func (ds Stack) Up(io stream.IOStream) error { - if ds.compose(io, "up", "-d") != 0 { + code, err := ds.compose(io, "up", "-d") + if err != nil { + return err + } + if code != 0 { return errStackUp } return nil @@ -53,7 +69,7 @@ func (ds Stack) Up(io stream.IOStream) error { // It is equivalent to 'docker compose exec $service $executable $args...'. // // It returns the exit code of the process. -func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...string) int { +func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...string) (int, error) { compose := []string{"exec"} if io.StdinIsATerminal() { compose = append(compose, "-ti") @@ -67,7 +83,7 @@ func (ds Stack) Exec(io stream.IOStream, service, executable string, args ...str // It is equivalent to 'docker compose run [--rm] $service $executable $args...'. // // It returns the exit code of the process. -func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string, args ...string) int { +func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string, args ...string) (int, error) { compose := []string{"run"} if autoRemove { compose = append(compose, "--rm") @@ -77,7 +93,12 @@ func (ds Stack) Run(io stream.IOStream, autoRemove bool, service, command string } compose = append(compose, command) compose = append(compose, args...) - return ds.compose(io, compose...) + + code, err := ds.compose(io, compose...) + if err != nil { + return execx.ExecCommandError, nil + } + return code, nil } var errStackRestart = errors.New("Stack.Restart: Restart returned non-zero exit code") @@ -85,7 +106,11 @@ var errStackRestart = errors.New("Stack.Restart: Restart returned non-zero exit // Restart restarts all containers in this Stack. // It is equivalent to 'docker compose restart' on the shell. func (ds Stack) Restart(io stream.IOStream) error { - if ds.compose(io, "restart") != 0 { + code, err := ds.compose(io, "restart") + if err != nil { + return err + } + if code != 0 { return errStackRestart } return nil @@ -96,7 +121,11 @@ var errStackDown = errors.New("Stack.Down: Down returned non-zero exit code") // Down stops and removes all containers in this Stack. // It is equivalent to 'docker compose down -v' on the shell. func (ds Stack) Down(io stream.IOStream) error { - if ds.compose(io, "down", "-v") != 0 { + code, err := ds.compose(io, "down", "-v") + if err != nil { + return err + } + if code != 0 { return errStackDown } return nil @@ -104,7 +133,7 @@ func (ds Stack) Down(io stream.IOStream) error { // Compose executes a 'docker compose' command on this stack. // TODO: This should be removed and replaced by an internal call directly to libcompose. -func (ds Stack) compose(io stream.IOStream, args ...string) int { +func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) { // TODO: can we migrate to a built-in version of this? - return execx.Compose(io, ds.Dir, args...) + return execx.Compose(io, ds.Dir, args...), nil }