Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/types/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import (
)

const (
ENV_PLATFORM = "SKUPPER_PLATFORM"
ENV_PLATFORM = "SKUPPER_PLATFORM"
ENV_SYSTEM_AUTO_RELOAD = "SKUPPER_SYSTEM_RELOAD_TYPE"

SystemReloadTypeAuto string = "auto"
SystemReloadTypeManual string = "manual"
)

type ConnectorCreateOptions struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/nonkube/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func Bootstrap(config *Config) (*api.SiteState, error) {
if err != nil {
return nil, fmt.Errorf("failed to load site state: %v", err)
}
// if sources are being consume from namespace sources, they must be properly set
// if sources are being consumed from namespace sources, they must be properly set
crNamespace := siteState.GetNamespace()
targetNamespace := utils.DefaultStr(config.Namespace, "default")
if config.InputPath == sourcesPath {
Expand Down
3 changes: 3 additions & 0 deletions internal/nonkube/bootstrap/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/skupperproject/skupper/internal/nonkube/bootstrap/controller"
internalclient "github.com/skupperproject/skupper/internal/nonkube/client/compat"
"github.com/skupperproject/skupper/internal/nonkube/common"
"github.com/skupperproject/skupper/internal/utils"
"github.com/skupperproject/skupper/pkg/container"
"github.com/skupperproject/skupper/pkg/nonkube/api"
)
Expand Down Expand Up @@ -75,6 +76,8 @@ func Install(platform string) error {
"CONTAINER_ENDPOINT": config.containerEndpoint,
"SKUPPER_OUTPUT_PATH": config.hostDataHome,
"CONTAINER_ENGINE": config.containerEngine,
"SKUPPER_SYSTEM_RELOAD_TYPE": utils.DefaultStr(os.Getenv(types.ENV_SYSTEM_AUTO_RELOAD),
types.SystemReloadTypeManual),
}

//To mount a volume as a bind, the host path must be specified in the Name field
Expand Down
15 changes: 12 additions & 3 deletions internal/nonkube/compat/site_state_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,19 @@ func (s *SiteStateRenderer) Render(loadedSiteState *api.SiteState, reload bool)
}
s.loadedSiteState = loadedSiteState
endpoint := os.Getenv("CONTAINER_ENDPOINT")
if endpoint == "" {
endpoint = fmt.Sprintf("unix://%s/podman/podman.sock", api.GetRuntimeDir())

// the container endpoint is mapped to the podman socket inside the container
if api.IsRunningInContainer() {
endpoint = "unix:///var/run/podman.sock"
if s.Platform == "docker" {
endpoint = "unix:///run/docker.sock"
endpoint = "unix:///var/run/docker.sock"
}
} else {
if endpoint == "" {
endpoint = fmt.Sprintf("unix://%s/podman/podman.sock", api.GetRuntimeDir())
if s.Platform == "docker" {
endpoint = "unix:///run/docker.sock"
}
}
}
s.cli, err = internalclient.NewCompatClient(endpoint, "")
Expand Down
116 changes: 116 additions & 0 deletions internal/nonkube/controller/input_resource_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package controller

import (
"fmt"
"log/slog"
"os"
"strings"
"sync"

"github.com/skupperproject/skupper/api/types"
"github.com/skupperproject/skupper/internal/cmd/skupper/common"
"github.com/skupperproject/skupper/internal/nonkube/bootstrap"
"github.com/skupperproject/skupper/internal/utils"
"github.com/skupperproject/skupper/pkg/nonkube/api"
)

// This feature is responsible for handling the creation of input resources and
// execute the start/reload of the site configuration automatically.
type InputResourceHandler struct {
logger *slog.Logger
namespace string
inputPath string
Bootstrap func(config *bootstrap.Config) (*api.SiteState, error)
PostExec func(config *bootstrap.Config, siteState *api.SiteState)
ConfigBootstrap bootstrap.Config
lock sync.Mutex
}

func NewInputResourceHandler(namespace string, inputPath string, bStrap func(config *bootstrap.Config) (*api.SiteState, error), postBootStrap func(config *bootstrap.Config, siteState *api.SiteState)) *InputResourceHandler {

systemReloadType := utils.DefaultStr(os.Getenv(types.ENV_SYSTEM_AUTO_RELOAD),
types.SystemReloadTypeManual)

if systemReloadType == types.SystemReloadTypeManual {
slog.Default().Debug("Automatic reloading is not configured.")
return nil
}

handler := &InputResourceHandler{
namespace: namespace,
inputPath: inputPath,
}

handler.Bootstrap = bStrap
handler.PostExec = postBootStrap

var binary string

platform := types.Platform(utils.DefaultStr(os.Getenv("CONTAINER_ENGINE"),
string(types.PlatformPodman)))

// TODO: add support for linux platform
switch common.Platform(platform) {
case common.PlatformDocker:
binary = "docker"
case common.PlatformPodman:
binary = "podman"
case common.PlatformLinux:
slog.Default().Error("Linux platform is not supported yet")
return nil
default:
slog.Default().Error("This platform value is not supported: ", slog.String("platform", string(platform)))
return nil
}

handler.ConfigBootstrap = bootstrap.Config{
Namespace: namespace,
InputPath: inputPath,
Platform: platform,
Binary: binary,
}

handler.logger = slog.Default().With("component", "input.resource.handler", "namespace", namespace)
return handler
}

func (h *InputResourceHandler) OnCreate(name string) {
h.logger.Info(fmt.Sprintf("Resource has been created: %s", name))
err := h.processInputFile()
if err != nil {
h.logger.Error(err.Error())
}
}

// This function does not need to be implemented, given that when a file is updated,
// the event OnCreate is triggered anyway. Having it implemented would cause
// the resources to be reloaded multiple times, stopping and starting a router pod.
// (issue: the router pod is still active while going to be deleted, and the controller
// tries to create a new router pod, failing on this)
func (h *InputResourceHandler) OnUpdate(name string) {}
func (h *InputResourceHandler) OnRemove(name string) {
h.logger.Info(fmt.Sprintf("Resource has been deleted: %s", name))
err := h.processInputFile()
if err != nil {
h.logger.Error(err.Error())
}
}
func (h *InputResourceHandler) Filter(name string) bool {
return strings.HasSuffix(name, ".json") || strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml")
}

func (h *InputResourceHandler) OnBasePathAdded(basePath string) {}

func (h *InputResourceHandler) processInputFile() error {
h.lock.Lock()
defer h.lock.Unlock()

siteState, err := h.Bootstrap(&h.ConfigBootstrap)
if err != nil {
return fmt.Errorf("Failed to bootstrap: %s", err)
}

h.PostExec(&h.ConfigBootstrap, siteState)

return nil
}
149 changes: 149 additions & 0 deletions internal/nonkube/controller/input_resource_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package controller

import (
"fmt"
"log/slog"
"testing"

"github.com/skupperproject/skupper/api/types"
"github.com/skupperproject/skupper/internal/nonkube/bootstrap"
"github.com/skupperproject/skupper/pkg/nonkube/api"
"gotest.tools/v3/assert"
)

func TestInputResourceHandler(t *testing.T) {

t.Run("handler created for docker platform", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "auto")
t.Setenv("CONTAINER_ENGINE", "docker")
inputResourceHandler := NewInputResourceHandler("test_namespace", "test_inputPath", mockBootstrap, mockPostExec)
expectedConfigBootstrap := bootstrap.Config{
Namespace: "test_namespace",
InputPath: "test_inputPath",
Platform: "docker",
Binary: "docker",
}

assert.Assert(t, inputResourceHandler != nil)
assert.Assert(t, inputResourceHandler.inputPath == "test_inputPath")
assert.Assert(t, inputResourceHandler.ConfigBootstrap == expectedConfigBootstrap)
})

t.Run("handler created for podman platform", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "auto")
t.Setenv("CONTAINER_ENGINE", "podman")
inputResourceHandler := NewInputResourceHandler("test_namespace", "test_inputPath", mockBootstrap, mockPostExec)
expectedConfigBootstrap := bootstrap.Config{
Namespace: "test_namespace",
InputPath: "test_inputPath",
Platform: "podman",
Binary: "podman",
}

assert.Assert(t, inputResourceHandler != nil)
assert.Assert(t, inputResourceHandler.inputPath == "test_inputPath")
assert.Assert(t, inputResourceHandler.ConfigBootstrap == expectedConfigBootstrap)
})

t.Run("handler not created for linux platform", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "auto")
t.Setenv("CONTAINER_ENGINE", "linux")
inputResourceHandler := NewInputResourceHandler("test_namespace", "test_inputPath", mockBootstrap, mockPostExec)

assert.Assert(t, inputResourceHandler == nil)

})

t.Run("handler not created because the system reload is configured to be manual", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "manual")
inputResourceHandler := NewInputResourceHandler("test_namespace", "test_inputPath", mockBootstrap, mockPostExec)

assert.Assert(t, inputResourceHandler == nil)

})

t.Run("handler not created for unknown platform", func(t *testing.T) {
t.Setenv("CONTAINER_ENGINE", "unknown")
inputResourceHandler := NewInputResourceHandler("test_namespace", "test_inputPath", mockBootstrap, mockPostExec)

assert.Assert(t, inputResourceHandler == nil)

})

t.Run("resource file created or updated", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "auto")
namespace := "test-file-created-ns"
inputPath := "test-file-created-input-path"

handler := NewInputResourceHandler(namespace, inputPath, mockBootstrap, mockPostExec)

logSpy := &testLogHandler{
handler: slog.Default().Handler(),
}
handler.logger = slog.New(logSpy)

resourceName := "site.yaml"
handler.OnCreate(resourceName)

expectedMsg := fmt.Sprintf("Resource has been created: %s", resourceName)
if count := logSpy.Count(expectedMsg); count != 1 {
t.Errorf("Expected log '%s' to be present, but found count: %d", expectedMsg, count)
}

})

t.Run("resource file removed", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "auto")
namespace := "test-file-ns"
inputPath := "test-file-input-path"

handler := NewInputResourceHandler(namespace, inputPath, mockBootstrap, mockPostExec)

logSpy := &testLogHandler{
handler: slog.Default().Handler(),
}
handler.logger = slog.New(logSpy)

resourceName := "site.yaml"
handler.OnRemove(resourceName)

expectedMsg := fmt.Sprintf("Resource has been deleted: %s", resourceName)
if count := logSpy.Count(expectedMsg); count != 1 {
t.Errorf("Expected log '%s' to be present, but found count: %d", expectedMsg, count)
}
})

t.Run("resource file created or updated but the reload fails", func(t *testing.T) {
t.Setenv(types.ENV_SYSTEM_AUTO_RELOAD, "auto")
namespace := "test-file-created-ns"
inputPath := "test-file-created-input-path"

handler := NewInputResourceHandler(namespace, inputPath, mockBootstrapFailed, mockPostExec)

logSpy := &testLogHandler{
handler: slog.Default().Handler(),
}
handler.logger = slog.New(logSpy)

resourceName := "site.yaml"
handler.OnCreate(resourceName)

expectedMsg := fmt.Sprintf("Failed to bootstrap: failed to bootstrap")
if count := logSpy.Count(expectedMsg); count != 1 {
t.Errorf("Expected log '%s' to be present, but found count: %d", expectedMsg, count)
}

})

}

func mockBootstrap(config *bootstrap.Config) (*api.SiteState, error) {
return api.NewSiteState(false), nil
}
func mockPostExec(config *bootstrap.Config, siteState *api.SiteState) {
fmt.Println("post bootstrap execution completed")
}

func mockBootstrapFailed(config *bootstrap.Config) (*api.SiteState, error) {
return nil, fmt.Errorf("failed to bootstrap")
}
22 changes: 17 additions & 5 deletions internal/nonkube/controller/namespace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import (
"log/slog"

"github.com/skupperproject/skupper/internal/filesystem"
"github.com/skupperproject/skupper/internal/nonkube/bootstrap"
"github.com/skupperproject/skupper/internal/nonkube/client/fs"
"github.com/skupperproject/skupper/pkg/nonkube/api"
)

type NamespaceController struct {
ns string
stopCh chan struct{}
logger *slog.Logger
watcher *filesystem.FileWatcher
prepare func()
ns string
stopCh chan struct{}
logger *slog.Logger
watcher *filesystem.FileWatcher
prepare func()
pathProvider fs.PathProvider
}

func NewNamespaceController(namespace string) (*NamespaceController, error) {
nsw := &NamespaceController{
ns: namespace,
stopCh: make(chan struct{}),
pathProvider: fs.PathProvider{
Namespace: namespace,
},
}
watcher, err := filesystem.NewWatcher(slog.String("namespace", namespace))
if err != nil {
Expand All @@ -39,8 +45,14 @@ func (w *NamespaceController) Start() {
routerConfigHandler.AddCallback(routerStateHandler)
collectorLifecycleHandler := NewCollectorLifecycleHandler(w.ns)
routerStateHandler.SetCallback(collectorLifecycleHandler)
inputResourceHandler := NewInputResourceHandler(w.ns, w.pathProvider.GetNamespace(), bootstrap.Bootstrap, bootstrap.PostBootstrap)

w.watcher.Add(api.GetInternalOutputPath(w.ns, api.RouterConfigPath), routerConfigHandler)
w.watcher.Add(api.GetInternalOutputPath(w.ns, api.RuntimeSiteStatePath), NewNetworkStatusHandler(w.ns))

if inputResourceHandler != nil {
w.watcher.Add(w.pathProvider.GetNamespace(), inputResourceHandler)
}
} else {
w.prepare()
}
Expand Down