Merge internal/stack => component
This commit is contained in:
parent
7b2f79bea1
commit
91a088a56a
16 changed files with 40 additions and 72 deletions
|
|
@ -3,7 +3,6 @@ package component
|
|||
|
||||
import (
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/config"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
// Component represents a logical subsystem of the distillery.
|
||||
|
|
@ -30,11 +29,11 @@ type Component interface {
|
|||
// Stack can be used to gain access to the "docker compose" stack.
|
||||
//
|
||||
// This should internally call
|
||||
Stack() stack.Installable
|
||||
Stack() Installable
|
||||
|
||||
// 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.
|
||||
Context(parent stack.InstallationContext) stack.InstallationContext
|
||||
Context(parent InstallationContext) InstallationContext
|
||||
}
|
||||
|
||||
// ComponentBase implements base functionality for a component
|
||||
|
|
@ -50,12 +49,12 @@ func (cb ComponentBase) Path() string {
|
|||
}
|
||||
|
||||
// Context passes through the parent context
|
||||
func (ComponentBase) Context(parent stack.InstallationContext) stack.InstallationContext {
|
||||
func (ComponentBase) Context(parent InstallationContext) InstallationContext {
|
||||
return parent
|
||||
}
|
||||
|
||||
// MakeStack registers the Installable as a stack
|
||||
func (cb ComponentBase) MakeStack(stack stack.Installable) stack.Installable {
|
||||
func (cb ComponentBase) MakeStack(stack Installable) Installable {
|
||||
stack.Dir = cb.Dir
|
||||
return stack
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/core"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
type Dis struct {
|
||||
|
|
@ -23,8 +22,8 @@ func (dis Dis) Name() string {
|
|||
//go:embed all:stack dis.env
|
||||
var resources embed.FS
|
||||
|
||||
func (dis Dis) Stack() stack.Installable {
|
||||
return dis.ComponentBase.MakeStack(stack.Installable{
|
||||
func (dis Dis) Stack() component.Installable {
|
||||
return dis.ComponentBase.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
ContextPath: "stack",
|
||||
EnvPath: "dis.env",
|
||||
|
|
@ -44,8 +43,8 @@ func (dis Dis) Stack() stack.Installable {
|
|||
})
|
||||
}
|
||||
|
||||
func (dis Dis) Context(parent stack.InstallationContext) stack.InstallationContext {
|
||||
return stack.InstallationContext{
|
||||
func (dis Dis) Context(parent component.InstallationContext) component.InstallationContext {
|
||||
return component.InstallationContext{
|
||||
core.Executable: dis.Executable,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
118
component/installable.go
Normal file
118
component/installable.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package component
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/fsx"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/unpack"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
)
|
||||
|
||||
// TODO: Move this package into components
|
||||
|
||||
// Installable represents a Stack that can be automatically installed from a set of resources
|
||||
// See the [Install] method.
|
||||
type Installable struct {
|
||||
Stack
|
||||
|
||||
// Installable enabled installing several resources from a (potentially embedded) filesystem.
|
||||
//
|
||||
// The Resources holds these, with appropriate resources specified below.
|
||||
// These all refer to paths within the Resource filesystem.
|
||||
Resources fs.FS
|
||||
ContextPath string // the 'docker compose' stack context, containing e.g. 'docker-compose.yml'.
|
||||
EnvPath string // the '.env' template, will be installed using [unpack.InstallTemplate].
|
||||
EnvContext map[string]string // context when instantiating the '.env' template
|
||||
|
||||
CopyContextFiles []string // Files to copy from the installation context
|
||||
|
||||
MakeDirsPerm fs.FileMode // permission for diretories, defaults to fs.ModeDir
|
||||
MakeDirs []string // directories to ensure that exist
|
||||
|
||||
TouchFiles []string // Files to 'touch', i.e. ensure that exist; guaranteed to be run after MakeDirs
|
||||
}
|
||||
|
||||
// InstallationContext is a context to install data in
|
||||
type InstallationContext map[string]string
|
||||
|
||||
// Install installs or updates this stack into the directory specified by stack.Stack().
|
||||
//
|
||||
// Installation is non-interactive, but will provide debugging output onto io.
|
||||
// InstallationContext
|
||||
func (is Installable) Install(io stream.IOStream, context InstallationContext) error {
|
||||
if is.ContextPath != "" {
|
||||
// setup the base files
|
||||
if err := unpack.InstallResource(
|
||||
is.Dir,
|
||||
is.ContextPath,
|
||||
is.Resources,
|
||||
func(dst, src string) {
|
||||
io.Printf("[install] %s\n", dst)
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// configure .env
|
||||
envDest := filepath.Join(is.Dir, ".env")
|
||||
if is.EnvPath != "" && is.EnvContext != nil {
|
||||
io.Printf("[config] %s\n", envDest)
|
||||
if err := unpack.InstallTemplate(
|
||||
envDest,
|
||||
is.EnvContext,
|
||||
is.EnvPath,
|
||||
is.Resources,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that certain dirs exist
|
||||
for _, name := range is.MakeDirs {
|
||||
// find the destination!
|
||||
dst := filepath.Join(is.Dir, name)
|
||||
|
||||
io.Printf("[make] %s\n", dst)
|
||||
if is.MakeDirsPerm == fs.FileMode(0) {
|
||||
is.MakeDirsPerm = fs.ModeDir
|
||||
}
|
||||
if err := os.MkdirAll(dst, is.MakeDirsPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// copy files from the context!
|
||||
for _, name := range is.CopyContextFiles {
|
||||
// find the source!
|
||||
src, ok := context[name]
|
||||
if !ok {
|
||||
return errors.Errorf("Missing file from context: %s", src)
|
||||
}
|
||||
|
||||
// find the destination!
|
||||
dst := filepath.Join(is.Dir, name)
|
||||
|
||||
// copy over file from context
|
||||
io.Printf("[copy] %s (from %s)\n", dst, src)
|
||||
if err := fsx.CopyFile(dst, src); err != nil {
|
||||
return errors.Wrapf(err, "Unable to copy file %s", src)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that certain files exist
|
||||
for _, name := range is.TouchFiles {
|
||||
// find the destination!
|
||||
dst := filepath.Join(is.Dir, name)
|
||||
|
||||
io.Printf("[touch] %s\n", dst)
|
||||
if err := fsx.Touch(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/FAU-CDI/wdresolve/resolvers"
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/core"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
)
|
||||
|
||||
|
|
@ -35,8 +34,8 @@ func (resolver Resolver) ConfigPath() string {
|
|||
//go:embed all:stack resolver.env
|
||||
var resources embed.FS
|
||||
|
||||
func (resolver Resolver) Stack() stack.Installable {
|
||||
return resolver.ComponentBase.MakeStack(stack.Installable{
|
||||
func (resolver Resolver) Stack() component.Installable {
|
||||
return resolver.ComponentBase.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
ContextPath: "stack",
|
||||
EnvPath: "resolver.env",
|
||||
|
|
@ -58,8 +57,8 @@ func (resolver Resolver) Stack() stack.Installable {
|
|||
})
|
||||
}
|
||||
|
||||
func (resolver Resolver) Context(parent stack.InstallationContext) stack.InstallationContext {
|
||||
return stack.InstallationContext{
|
||||
func (resolver Resolver) Context(parent component.InstallationContext) component.InstallationContext {
|
||||
return component.InstallationContext{
|
||||
core.Executable: resolver.Executable,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"embed"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
type Self struct {
|
||||
|
|
@ -19,14 +18,14 @@ func (Self) Name() string {
|
|||
//go:embed self.env
|
||||
var resources embed.FS
|
||||
|
||||
func (self Self) Stack() stack.Installable {
|
||||
func (self Self) Stack() component.Installable {
|
||||
// TODO: Move me into config!
|
||||
TARGET := "https://github.com/FAU-CDI/wisski-distillery"
|
||||
if self.Config.SelfRedirect != nil { // TODO: move to config!
|
||||
TARGET = self.Config.SelfRedirect.String()
|
||||
}
|
||||
|
||||
return self.ComponentBase.MakeStack(stack.Installable{
|
||||
return self.ComponentBase.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
|
||||
ContextPath: "stack",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
type SQL struct {
|
||||
|
|
@ -26,8 +25,8 @@ func (SQL) Name() string {
|
|||
//go:embed all:stack
|
||||
var resources embed.FS
|
||||
|
||||
func (ssh SQL) Stack() stack.Installable {
|
||||
return ssh.ComponentBase.MakeStack(stack.Installable{
|
||||
func (ssh SQL) Stack() component.Installable {
|
||||
return ssh.ComponentBase.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
ContextPath: "stack",
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"embed"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
type SSH struct {
|
||||
|
|
@ -18,8 +17,8 @@ func (SSH) Name() string {
|
|||
//go:embed all:stack
|
||||
var resources embed.FS
|
||||
|
||||
func (ssh SSH) Stack() stack.Installable {
|
||||
return ssh.ComponentBase.MakeStack(stack.Installable{
|
||||
func (ssh SSH) Stack() component.Installable {
|
||||
return ssh.ComponentBase.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
ContextPath: "stack",
|
||||
})
|
||||
|
|
|
|||
150
component/stack.go
Normal file
150
component/stack.go
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Package stack implements a docker compose stack
|
||||
package component
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/execx"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
)
|
||||
|
||||
// Stack represents a 'docker compose' stack living in a specific directory
|
||||
//
|
||||
// NOTE(twiesing): In the current implementation this requires a 'docker' executable on the system.
|
||||
// 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 {
|
||||
Dir string // Directory this Stack is located in
|
||||
|
||||
DockerExecutable string // Path to the native docker executable to use
|
||||
}
|
||||
|
||||
var errStackUpdatePull = errors.New("Stack.Update: Pull returned non-zero exit code")
|
||||
var errStackUpdateBuild = errors.New("Stack.Update: Build returned non-zero exit code")
|
||||
|
||||
// Update pulls, builds, and then optionally starts this stack.
|
||||
// This does not have a direct 'docker compose' shell equivalent.
|
||||
//
|
||||
// See also Up.
|
||||
func (ds Stack) Update(io stream.IOStream, start bool) error {
|
||||
{
|
||||
code, err := ds.compose(io, "pull")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return errStackUpdatePull
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
code, err := ds.compose(io, "build", "--pull")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return errStackUpdateBuild
|
||||
}
|
||||
}
|
||||
if start {
|
||||
return ds.Up(io)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 --remove-orphans --detach' on the shell.
|
||||
func (ds Stack) Up(io stream.IOStream) error {
|
||||
code, err := ds.compose(io, "up", "--remove-orphans", "--detach")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return errStackUp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec executes an executable in the provided running service.
|
||||
// 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, error) {
|
||||
compose := []string{"exec"}
|
||||
if io.StdinIsATerminal() {
|
||||
compose = append(compose, "-ti")
|
||||
}
|
||||
compose = append(compose, service)
|
||||
compose = append(compose, executable)
|
||||
compose = append(compose, args...)
|
||||
return ds.compose(io, compose...)
|
||||
}
|
||||
|
||||
// Run executes the provided service with the given executable.
|
||||
// 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, error) {
|
||||
compose := []string{"run"}
|
||||
if autoRemove {
|
||||
compose = append(compose, "--rm")
|
||||
}
|
||||
if !io.StdinIsATerminal() {
|
||||
compose = append(compose, "-T")
|
||||
}
|
||||
compose = append(compose, service, command)
|
||||
compose = append(compose, args...)
|
||||
|
||||
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")
|
||||
|
||||
// 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 {
|
||||
code, err := ds.compose(io, "restart")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return errStackRestart
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
code, err := ds.compose(io, "down", "-v")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return errStackDown
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compose executes a 'docker compose' command on this stack.
|
||||
//
|
||||
// NOTE(twiesing): Check if this can be replaced by an internal call to libcompose.
|
||||
// But probably not.
|
||||
func (ds Stack) compose(io stream.IOStream, args ...string) (int, error) {
|
||||
if ds.DockerExecutable == "" {
|
||||
var err error
|
||||
ds.DockerExecutable, err = execx.LookPathAbs("docker")
|
||||
if err != nil {
|
||||
return execx.ExecCommandError, err
|
||||
}
|
||||
}
|
||||
return execx.Exec(io, ds.Dir, ds.DockerExecutable, append([]string{"compose"}, args...)...), nil
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
type Triplestore struct {
|
||||
|
|
@ -27,8 +26,8 @@ func (Triplestore) Name() string {
|
|||
//go:embed all:stack
|
||||
var resources embed.FS
|
||||
|
||||
func (ts Triplestore) Stack() stack.Installable {
|
||||
return ts.ComponentBase.MakeStack(stack.Installable{
|
||||
func (ts Triplestore) Stack() component.Installable {
|
||||
return ts.ComponentBase.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
ContextPath: "stack",
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"embed"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/stack"
|
||||
)
|
||||
|
||||
// Web implements the web component
|
||||
|
|
@ -20,13 +19,13 @@ func (Web) Name() string {
|
|||
//go:embed web.env
|
||||
var resources embed.FS
|
||||
|
||||
func (web Web) Stack() stack.Installable {
|
||||
func (web Web) Stack() component.Installable {
|
||||
HTTPS_METHOD := "nohttp"
|
||||
if web.Config.HTTPSEnabled() {
|
||||
HTTPS_METHOD = "redirect"
|
||||
}
|
||||
|
||||
return web.MakeStack(stack.Installable{
|
||||
return web.MakeStack(component.Installable{
|
||||
Resources: resources,
|
||||
ContextPath: "stack",
|
||||
EnvPath: "web.env",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue