WIP: config live-reload
This commit is contained in:
parent
f169b7f137
commit
adc6496c4c
47
cmd/root.go
47
cmd/root.go
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/api/plugin"
|
"github.com/Neur0toxine/sshpoke/internal/api/plugin"
|
||||||
"github.com/Neur0toxine/sshpoke/internal/api/rest"
|
"github.com/Neur0toxine/sshpoke/internal/api/rest"
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"github.com/Neur0toxine/sshpoke/internal/server"
|
"github.com/Neur0toxine/sshpoke/internal/server"
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||||
plugin2 "github.com/Neur0toxine/sshpoke/pkg/plugin"
|
plugin2 "github.com/Neur0toxine/sshpoke/pkg/plugin"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -30,12 +32,28 @@ var rootCmd = &cobra.Command{
|
|||||||
Short: "Expose your Docker services to the Internet via SSH.",
|
Short: "Expose your Docker services to the Internet via SSH.",
|
||||||
Long: `sshpoke is a CLI application that listens to the docker socket and automatically exposes relevant services to the Internet.`,
|
Long: `sshpoke is a CLI application that listens to the docker socket and automatically exposes relevant services to the Internet.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
config.Rehash()
|
||||||
server.DefaultManager = server.NewManager(ctx, config.Default.Servers, config.Default.DefaultServer)
|
viper.WatchConfig()
|
||||||
runPluginServer()
|
cancel, shutdown := initApp()
|
||||||
runRestServer()
|
viper.OnConfigChange(func(in fsnotify.Event) {
|
||||||
runDockerEventListener(ctx)
|
if !in.Op.Has(fsnotify.Write) {
|
||||||
shutdown := makeShutdownFunc(cancel)
|
return
|
||||||
|
}
|
||||||
|
initConfig()
|
||||||
|
if !config.HasBeenUpdated() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Rehash()
|
||||||
|
logger.Default.Info("configuration has been updated, restarting the app...")
|
||||||
|
cancel()
|
||||||
|
go server.DefaultManager.WaitForShutdown()
|
||||||
|
ctx, innerCancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer innerCancel()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
initApp()
|
||||||
|
})
|
||||||
|
|
||||||
linuxSig := make(chan os.Signal, 1)
|
linuxSig := make(chan os.Signal, 1)
|
||||||
signal.Notify(linuxSig)
|
signal.Notify(linuxSig)
|
||||||
@ -50,6 +68,15 @@ var rootCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initApp() (func(), func(os.Signal)) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
server.DefaultManager = server.NewManager(ctx, config.Default.Servers, config.Default.DefaultServer)
|
||||||
|
runPluginServer(ctx)
|
||||||
|
runRestServer(ctx)
|
||||||
|
runDockerEventListener(ctx)
|
||||||
|
return cancel, makeShutdownFunc(cancel)
|
||||||
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
err := rootCmd.Execute()
|
err := rootCmd.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,20 +125,20 @@ func initConfig() {
|
|||||||
logger.Sugar.Debugw("configuration loaded", "config", config.Default)
|
logger.Sugar.Debugw("configuration loaded", "config", config.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPluginServer() {
|
func runPluginServer(ctx context.Context) {
|
||||||
port := config.Default.API.Plugin.Port
|
port := config.Default.API.Plugin.Port
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = plugin2.DefaultPort
|
port = plugin2.DefaultPort
|
||||||
}
|
}
|
||||||
go plugin.StartServer(port, logger.Sugar.With("component", "pluginServer"))
|
go plugin.StartServer(ctx, port, logger.Sugar.With("component", "pluginServer"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRestServer() {
|
func runRestServer(ctx context.Context) {
|
||||||
port := config.Default.API.Rest.Port
|
port := config.Default.API.Rest.Port
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = rest.DefaultPort
|
port = rest.DefaultPort
|
||||||
}
|
}
|
||||||
go rest.StartServer(
|
go rest.StartServer(ctx,
|
||||||
config.Default.API.Rest.Token, port, logger.Sugar.With("component", "webServer"), config.Default.Debug)
|
config.Default.API.Rest.Token, port, logger.Sugar.With("component", "webServer"), config.Default.Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -5,6 +5,7 @@ go 1.21.4
|
|||||||
require (
|
require (
|
||||||
github.com/docker/docker v24.0.7+incompatible
|
github.com/docker/docker v24.0.7+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/function61/gokit v0.0.0-20231117065306-355fe206d542
|
github.com/function61/gokit v0.0.0-20231117065306-355fe206d542
|
||||||
github.com/gin-contrib/secure v0.0.1
|
github.com/gin-contrib/secure v0.0.1
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
@ -39,7 +40,6 @@ require (
|
|||||||
github.com/distribution/reference v0.5.0 // indirect
|
github.com/distribution/reference v0.5.0 // indirect
|
||||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
@ -59,7 +59,7 @@ func (p *pluginAPI) receiverForContext(ctx context.Context) plugin.Plugin {
|
|||||||
return server.DefaultManager.PluginByToken(tokens[0])
|
return server.DefaultManager.PluginByToken(tokens[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartServer(port int, log *zap.SugaredLogger) {
|
func StartServer(ctx context.Context, port int, log *zap.SugaredLogger) {
|
||||||
socket, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
socket, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("cannot start plugin API server on port %d: %s", port, err)
|
log.Errorf("cannot start plugin API server on port %d: %s", port, err)
|
||||||
@ -68,6 +68,11 @@ func StartServer(port int, log *zap.SugaredLogger) {
|
|||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
pb.RegisterPluginServiceServer(s, &pluginAPI{log: log})
|
pb.RegisterPluginServiceServer(s, &pluginAPI{log: log})
|
||||||
log.Debugf("starting plugin server on :%d", port)
|
log.Debugf("starting plugin server on :%d", port)
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
s.GracefulStop()
|
||||||
|
socket.Close()
|
||||||
|
}()
|
||||||
if err := s.Serve(socket); err != nil {
|
if err := s.Serve(socket); err != nil {
|
||||||
log.Fatalf("cannot start plugin server on :%d: %s", port, err)
|
log.Fatalf("cannot start plugin server on :%d: %s", port, err)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/api/rest/handler"
|
"github.com/Neur0toxine/sshpoke/internal/api/rest/handler"
|
||||||
@ -27,7 +30,7 @@ const (
|
|||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @description Rest API token (leave empty if it's not provided in config).
|
// @description Rest API token (leave empty if it's not provided in config).
|
||||||
|
|
||||||
func StartServer(token string, port int, log *zap.SugaredLogger, debug bool) {
|
func StartServer(ctx context.Context, token string, port int, log *zap.SugaredLogger, debug bool) {
|
||||||
if !debug {
|
if !debug {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
@ -45,8 +48,18 @@ func StartServer(token string, port int, log *zap.SugaredLogger, debug bool) {
|
|||||||
}))
|
}))
|
||||||
g.Use(secure.New(secureConfig()))
|
g.Use(secure.New(secureConfig()))
|
||||||
router(g, token)
|
router(g, token)
|
||||||
err := g.Run(net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
srv := &http.Server{
|
||||||
if err != nil {
|
Addr: net.JoinHostPort("127.0.0.1", strconv.Itoa(port)),
|
||||||
|
Handler: g,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
|
logger.Sugar.Errorf("failed to stop the server gracefully: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err := srv.ListenAndServe()
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
logger.Sugar.Errorf("cannot start Rest API server on port %d: %s", port, err)
|
logger.Sugar.Errorf("cannot start Rest API server on port %d: %s", port, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -9,7 +12,10 @@ import (
|
|||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Default Config
|
var (
|
||||||
|
Default Config
|
||||||
|
configHash string
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Debug bool `mapstructure:"debug" json:"debug"`
|
Debug bool `mapstructure:"debug" json:"debug"`
|
||||||
@ -20,6 +26,20 @@ type Config struct {
|
|||||||
Servers []Server `mapstructure:"servers" json:"servers"`
|
Servers []Server `mapstructure:"servers" json:"servers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HasBeenUpdated() bool {
|
||||||
|
return generateConfigHash() != configHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rehash() {
|
||||||
|
configHash = generateConfigHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateConfigHash() string {
|
||||||
|
h := sha1.New()
|
||||||
|
_ = gob.NewEncoder(h).Encode(Default)
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Name smarttypes.MatchableString `mapstructure:"name" json:"name"`
|
Name smarttypes.MatchableString `mapstructure:"name" json:"name"`
|
||||||
Params ServiceLabels `mapstructure:"params" json:"params"`
|
Params ServiceLabels `mapstructure:"params" json:"params"`
|
||||||
|
Loading…
Reference in New Issue
Block a user