diff --git a/internal/dis/component/server/admin/admin.go b/internal/dis/component/server/admin/admin.go index e85091b..e9a2023 100644 --- a/internal/dis/component/server/admin/admin.go +++ b/internal/dis/component/server/admin/admin.go @@ -7,9 +7,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger" - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/julienschmidt/httprouter" "github.com/rs/zerolog" @@ -24,9 +22,7 @@ type Admin struct { Dependencies struct { Fetchers []component.DistilleryFetcher - Exporter *exporter.Exporter - Instances *instances.Instances - SnapshotsLog *logger.Logger + Instances *instances.Instances Auth *auth.Auth @@ -34,7 +30,7 @@ type Admin struct { Templating *templating.Templating - Purger *purger.Purger + Sockets *socket.Sockets } Analytics *lazy.PoolAnalytics @@ -75,7 +71,7 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http handler = &httpx.WebSocket{ Context: ctx, Fallback: router, - Handler: admin.serveSocket, + Handler: admin.Dependencies.Sockets.Serve, } } diff --git a/internal/dis/component/server/admin/html/index.html b/internal/dis/component/server/admin/html/index.html index 458e02f..2fe2a0b 100644 --- a/internal/dis/component/server/admin/html/index.html +++ b/internal/dis/component/server/admin/html/index.html @@ -180,6 +180,9 @@

Backups

+

+ +

diff --git a/internal/dis/component/server/admin/socket.go b/internal/dis/component/server/admin/socket.go deleted file mode 100644 index fb41a06..0000000 --- a/internal/dis/component/server/admin/socket.go +++ /dev/null @@ -1,158 +0,0 @@ -package admin - -import ( - "context" - "fmt" - "io" - "time" - - "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" - "github.com/FAU-CDI/wisski-distillery/internal/wisski" - "github.com/FAU-CDI/wisski-distillery/pkg/httpx" - "github.com/tkw1536/goprogram/status" -) - -type InstanceAction struct { - NumParams int - - HandleInteractive func(ctx context.Context, info *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error -} - -func (ia *InstanceAction) AsGenericAction() GenericAction { - return GenericAction{ - NumParams: ia.NumParams + 1, - HandleInteractive: func(ctx context.Context, info *Admin, out io.Writer, params ...string) error { - instance, err := info.Dependencies.Instances.WissKI(ctx, params[0]) - if err != nil { - return err - } - - return ia.HandleInteractive(ctx, info, instance, out, params[1:]...) - }, - } -} - -type GenericAction struct { - NumParams int - - HandleInteractive func(ctx context.Context, info *Admin, out io.Writer, params ...string) error -} - -// non-instance specific actions -var genericActions = map[string]GenericAction{} - -// socket specific actions -var socketInstanceActions = map[string]InstanceAction{ - "snapshot": { - HandleInteractive: func(ctx context.Context, admin *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error { - return admin.Dependencies.Exporter.MakeExport( - ctx, - out, - exporter.ExportTask{ - Dest: "", - Instance: instance, - - StagingOnly: false, - }, - ) - }, - }, - "rebuild": { - HandleInteractive: func(ctx context.Context, _ *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error { - return instance.Barrel().Build(ctx, out, true) - }, - }, - "update": { - HandleInteractive: func(ctx context.Context, _ *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error { - return instance.Drush().Update(ctx, out) - }, - }, - "cron": { - HandleInteractive: func(ctx context.Context, _ *Admin, instance *wisski.WissKI, str io.Writer, params ...string) error { - return instance.Drush().Cron(ctx, str) - }, - }, - "start": { - HandleInteractive: func(ctx context.Context, _ *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error { - return instance.Barrel().Stack().Up(ctx, out) - }, - }, - "stop": { - HandleInteractive: func(ctx context.Context, _ *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error { - return instance.Barrel().Stack().Down(ctx, out) - }, - }, - "purge": { - HandleInteractive: func(ctx context.Context, admin *Admin, instance *wisski.WissKI, out io.Writer, params ...string) error { - return admin.Dependencies.Purger.Purge(ctx, out, instance.Slug) - }, - }, -} - -var socketGenericActions = func() map[string]GenericAction { - generics := make(map[string]GenericAction, len(socketInstanceActions)) - for n, a := range socketInstanceActions { - generics[n] = a.AsGenericAction() - } - return generics -}() - -func (admin *Admin) serveSocket(conn httpx.WebSocketConnection) { - // read the next message to act on - message, ok := <-conn.Read() - if !ok { - return - } - - name := string(message.Bytes) - - // perform a generic action first - if action, ok := genericActions[name]; ok { - admin.handleGenericAction(conn, action) - return - } - - // then do the socket actions - if action, ok := socketGenericActions[name]; ok { - admin.handleGenericAction(conn, action) - } -} - -var instanceParamsTimeout = time.Second - -func (admin *Admin) handleGenericAction(conn httpx.WebSocketConnection, action GenericAction) { - // read the parameters - params := make([]string, action.NumParams) - for i := range params { - select { - case message, ok := <-conn.Read(): - if !ok { - <-conn.WriteText("Insufficient parameters") - return - } - params[i] = string(message.Bytes) - case <-time.After(instanceParamsTimeout): - <-conn.WriteText("Timed out reading parameters") - return - } - } - - // build a stream - writer := &status.LineBuffer{ - Line: func(line string) { - <-conn.WriteText(line) - }, - FlushLineOnClose: true, - } - defer writer.Close() - - // handle the interactive action - if action.HandleInteractive != nil { - err := action.HandleInteractive(conn.Context(), admin, writer, params...) - if err != nil { - fmt.Fprintln(writer, err) - return - } - fmt.Fprintln(writer, "done") - } -} diff --git a/internal/dis/component/server/admin/socket/actions.go b/internal/dis/component/server/admin/socket/actions.go new file mode 100644 index 0000000..40dc86d --- /dev/null +++ b/internal/dis/component/server/admin/socket/actions.go @@ -0,0 +1,83 @@ +package socket + +import ( + "context" + "io" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" +) + +// non-instance specific actions +var actions = map[string]SocketAction{ + "backup": { + HandleInteractive: func(ctx context.Context, socket *Sockets, out io.Writer, params ...string) error { + return socket.Dependencies.Exporter.MakeExport( + ctx, + out, + exporter.ExportTask{ + Dest: "", + Instance: nil, + + StagingOnly: false, + }, + ) + }, + }, +} + +// socket specific actions +var iActions = map[string]IAction{ + "snapshot": { + HandleInteractive: func(ctx context.Context, socket *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error { + return socket.Dependencies.Exporter.MakeExport( + ctx, + out, + exporter.ExportTask{ + Dest: "", + Instance: instance, + + StagingOnly: false, + }, + ) + }, + }, + "rebuild": { + HandleInteractive: func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error { + return instance.Barrel().Build(ctx, out, true) + }, + }, + "update": { + HandleInteractive: func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error { + return instance.Drush().Update(ctx, out) + }, + }, + "cron": { + HandleInteractive: func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, str io.Writer, params ...string) error { + return instance.Drush().Cron(ctx, str) + }, + }, + "start": { + HandleInteractive: func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error { + return instance.Barrel().Stack().Up(ctx, out) + }, + }, + "stop": { + HandleInteractive: func(ctx context.Context, _ *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error { + return instance.Barrel().Stack().Down(ctx, out) + }, + }, + "purge": { + HandleInteractive: func(ctx context.Context, sockets *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error { + return sockets.Dependencies.Purger.Purge(ctx, out, instance.Slug) + }, + }, +} + +var igActions = func() map[string]SocketAction { + generics := make(map[string]SocketAction, len(iActions)) + for n, a := range iActions { + generics[n] = a.AsGenericAction() + } + return generics +}() diff --git a/internal/dis/component/server/admin/socket/socket.go b/internal/dis/component/server/admin/socket/socket.go new file mode 100644 index 0000000..4ef0fb2 --- /dev/null +++ b/internal/dis/component/server/admin/socket/socket.go @@ -0,0 +1,116 @@ +package socket + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/pkg/httpx" + "github.com/tkw1536/goprogram/status" +) + +type Sockets struct { + component.Base + + Dependencies struct { + Instances *instances.Instances + Exporter *exporter.Exporter + Purger *purger.Purger + } +} + +// Serve handles a connection to the websocket api +func (socket *Sockets) Serve(conn httpx.WebSocketConnection) { + // read the next message to act on + message, ok := <-conn.Read() + if !ok { + return + } + + name := string(message.Bytes) + + // perform a generic action first + if action, ok := actions[name]; ok { + socket.Handle(conn, action) + return + } + + // then do the socket actions + if action, ok := igActions[name]; ok { + socket.Handle(conn, action) + } +} + +var instanceParamsTimeout = time.Second + +func (socket *Sockets) Handle(conn httpx.WebSocketConnection, action SocketAction) { + // read the parameters + params := make([]string, action.NumParams) + for i := range params { + select { + case message, ok := <-conn.Read(): + if !ok { + <-conn.WriteText("Insufficient parameters") + return + } + params[i] = string(message.Bytes) + case <-time.After(instanceParamsTimeout): + <-conn.WriteText("Timed out reading parameters") + return + } + } + + // build a stream + writer := &status.LineBuffer{ + Line: func(line string) { + <-conn.WriteText(line) + }, + FlushLineOnClose: true, + } + defer writer.Close() + + // handle the interactive action + if action.HandleInteractive != nil { + err := action.HandleInteractive(conn.Context(), socket, writer, params...) + if err != nil { + fmt.Fprintln(writer, err) + return + } + fmt.Fprintln(writer, "done") + } +} + +// IAction is like SocketAction, but takes the slug of an instance (runnning or not) as the first parameter +type IAction struct { + NumParams int + + HandleInteractive func(ctx context.Context, sockets *Sockets, instance *wisski.WissKI, out io.Writer, params ...string) error +} + +// AsGenericAction turns this InstanceAction into a generic action +func (ia IAction) AsGenericAction() SocketAction { + return SocketAction{ + NumParams: ia.NumParams + 1, + HandleInteractive: func(ctx context.Context, sockets *Sockets, out io.Writer, params ...string) error { + instance, err := sockets.Dependencies.Instances.WissKI(ctx, params[0]) + if err != nil { + return err + } + + return ia.HandleInteractive(ctx, sockets, instance, out, params[1:]...) + }, + } +} + +// SocketAction represents an action handled via socket +type SocketAction struct { + NumParams int + + HandleInteractive func(ctx context.Context, sockets *Sockets, out io.Writer, params ...string) error +} diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index 39797a4..dc0a942 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -20,6 +20,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/resolver" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/cron" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/home" @@ -186,6 +187,7 @@ func (dis *Distillery) allComponents() []initFunc { manual(func(admin *admin.Admin) { admin.Analytics = &dis.pool.Analytics }), + auto[*socket.Sockets], auto[*legal.Legal], auto[*news.News],