Repository: owncloud/ocis
Pull request #6071 (2023-04-18 09:34:41)

[full-ci] add optional services and fix config slice parser
## Description

Enhancement: Add optional services to the runtime

Make it possible to start optional services in the ocis runtime. Instead of using `OCIS_RUN_SERVICES` to define all services we can now use `OCIS_ADD_RUN_SERVICES` to add a comma separated list of additional services which are not started in the single process by default.

## Fix

Our envdecoder was parsing string slices differently than documented. It was using `;` as a separator.

@wkloucek @mmattel FYI

## Motivation and Context

Make the usage of the single process runtime easier

## How Has This Been Tested?
- manualle

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [x] Code changes
- [x] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 



Patch:
diff --git a/changelog/unreleased/runtime.md b/changelog/unreleased/runtime.md
new file mode 100644
index 00000000000..aa096595371
--- /dev/null
+++ b/changelog/unreleased/runtime.md
@@ -0,0 +1,5 @@
+Enhancement: Add optional services to the runtime
+
+Make it possible to start optional services in the ocis runtime. Instead of using `OCIS_RUN_SERVICES` to define all services we can now use `OCIS_ADD_RUN_SERVICES` to add a comma separated list of additional services which are not started in the single process by default.
+
+https://github.com/owncloud/ocis/pull/6071
diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go
index 626b4b6928c..8fb9c7a5d14 100644
--- a/ocis-pkg/config/config.go
+++ b/ocis-pkg/config/config.go
@@ -44,10 +44,11 @@ type Mode int
 
 // Runtime configures the oCIS runtime when running in supervised mode.
 type Runtime struct {
-	Port     string `yaml:"port" env:"OCIS_RUNTIME_PORT"`
-	Host     string `yaml:"host" env:"OCIS_RUNTIME_HOST"`
-	Services string `yaml:"services" env:"OCIS_RUN_EXTENSIONS;OCIS_RUN_SERVICES" desc:"A comma-separated list of service names. Will start only the listed services."`
-	Disabled string `yaml:"disabled_services" env:"OCIS_EXCLUDE_RUN_SERVICES" desc:"A comma-separated list of service names. Will start all services except of the ones listed. Has no effect when OCIS_RUN_SERVICES is set."`
+	Port       string   `yaml:"port" env:"OCIS_RUNTIME_PORT"`
+	Host       string   `yaml:"host" env:"OCIS_RUNTIME_HOST"`
+	Services   []string `yaml:"services" env:"OCIS_RUN_EXTENSIONS;OCIS_RUN_SERVICES" desc:"A comma-separated list of service names. Will start only the listed services."`
+	Disabled   []string `yaml:"disabled_services" env:"OCIS_EXCLUDE_RUN_SERVICES" desc:"A comma-separated list of service names. Will start all services except of the ones listed. Has no effect when OCIS_RUN_SERVICES is set."`
+	Additional []string `yaml:"add_services" env:"OCIS_ADD_RUN_SERVICES" desc:"A comma-separated list of service names. Will add the listed services to the default configuration. Has no effect when OCIS_RUN_SERVICES is set."`
 }
 
 // Config combines all available configuration parts.
diff --git a/ocis-pkg/config/envdecode/envdecode.go b/ocis-pkg/config/envdecode/envdecode.go
index 8a26b84dff1..60d030dd2f1 100644
--- a/ocis-pkg/config/envdecode/envdecode.go
+++ b/ocis-pkg/config/envdecode/envdecode.go
@@ -214,7 +214,7 @@ func decode(target interface{}, strict bool) (int, error) {
 }
 
 func decodeSlice(f *reflect.Value, env string) error {
-	parts := strings.Split(env, ";")
+	parts := strings.Split(env, ",")
 
 	values := parts[:0]
 	for _, x := range parts {
diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go
index 06cc367ed94..eec8b87e9c0 100644
--- a/ocis/pkg/runtime/service/service.go
+++ b/ocis/pkg/runtime/service/service.go
@@ -18,8 +18,10 @@ import (
 	ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/ocis-pkg/shared"
+	antivirus "github.com/owncloud/ocis/v2/services/antivirus/pkg/command"
 	appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command"
 	appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/command"
+	audit "github.com/owncloud/ocis/v2/services/audit/pkg/command"
 	authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command"
 	authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command"
 	eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command"
@@ -34,6 +36,7 @@ import (
 	notifications "github.com/owncloud/ocis/v2/services/notifications/pkg/command"
 	ocdav "github.com/owncloud/ocis/v2/services/ocdav/pkg/command"
 	ocs "github.com/owncloud/ocis/v2/services/ocs/pkg/command"
+	policies "github.com/owncloud/ocis/v2/services/policies/pkg/command"
 	postprocessing "github.com/owncloud/ocis/v2/services/postprocessing/pkg/command"
 	proxy "github.com/owncloud/ocis/v2/services/proxy/pkg/command"
 	search "github.com/owncloud/ocis/v2/services/search/pkg/command"
@@ -65,6 +68,7 @@ type Service struct {
 	Supervisor       *suture.Supervisor
 	ServicesRegistry serviceFuncMap
 	Delayed          serviceFuncMap
+	Additional       serviceFuncMap
 	Log              log.Logger
 
 	serviceToken map[string][]suture.ServiceToken
@@ -96,6 +100,7 @@ func NewService(options ...Option) (*Service, error) {
 	s := &Service{
 		ServicesRegistry: make(serviceFuncMap),
 		Delayed:          make(serviceFuncMap),
+		Additional:       make(serviceFuncMap),
 		Log:              l,
 
 		serviceToken: make(map[string][]suture.ServiceToken),
@@ -134,6 +139,11 @@ func NewService(options ...Option) (*Service, error) {
 	s.ServicesRegistry[opts.Config.EventHistory.Service.Name] = eventhistory.NewSutureService
 	s.ServicesRegistry[opts.Config.Userlog.Service.Name] = userlog.NewSutureService
 
+	// populate optional services
+	s.Additional[opts.Config.Antivirus.Service.Name] = antivirus.NewSutureService
+	s.Additional[opts.Config.Audit.Service.Name] = audit.NewSutureService
+	s.Additional[opts.Config.Policies.Service.Name] = policies.NewSutureService
+
 	// populate delayed services
 	s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSutureService
 	s.Delayed[opts.Config.Proxy.Service.Name] = proxy.NewSutureService
@@ -212,6 +222,9 @@ func Start(o ...Option) error {
 	// schedule services that we are sure don't have interdependencies.
 	scheduleServiceTokens(s, s.ServicesRegistry)
 
+	// schedule services that are optional
+	scheduleServiceTokens(s, s.Additional)
+
 	// there are reasons not to do this, but we have race conditions ourselves. Until we resolve them, mind the following disclaimer:
 	// Calling ServeBackground will CORRECTLY start the supervisor running in a new goroutine. It is risky to directly run
 	// go supervisor.Serve()
@@ -245,9 +258,8 @@ func scheduleServiceTokens(s *Service, funcSet serviceFuncMap) {
 // the runtime.
 func (s *Service) generateRunSet(cfg *ociscfg.Config) {
 	runset = make(map[string]struct{})
-	if cfg.Runtime.Services != "" {
-		e := strings.Split(strings.ReplaceAll(cfg.Runtime.Services, " ", ""), ",")
-		for _, name := range e {
+	if cfg.Runtime.Services != nil {
+		for _, name := range cfg.Runtime.Services {
 			runset[name] = struct{}{}
 		}
 		return
@@ -261,12 +273,16 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) {
 		runset[name] = struct{}{}
 	}
 
-	if cfg.Runtime.Disabled != "" {
-		e := strings.Split(strings.ReplaceAll(cfg.Runtime.Disabled, " ", ""), ",")
-		for _, name := range e {
-			delete(runset, name)
-		}
+	// add additional services if explicitly added by config
+	for _, name := range cfg.Runtime.Additional {
+		runset[name] = struct{}{}
 	}
+
+	// remove services if explicitly excluded by config
+	for _, name := range cfg.Runtime.Disabled {
+		delete(runset, name)
+	}
+
 }
 
 // List running processes for the Service Controller.


Test patch:
diff --git a/ocis-pkg/config/envdecode/envdecode_test.go b/ocis-pkg/config/envdecode/envdecode_test.go
index cb3274c1c64..03eb2638587 100644
--- a/ocis-pkg/config/envdecode/envdecode_test.go
+++ b/ocis-pkg/config/envdecode/envdecode_test.go
@@ -58,7 +58,7 @@ type testConfig struct {
 	UnmarshalerNumber unmarshalerNumber `env:"TEST_UNMARSHALER_NUMBER"`
 
 	DefaultInt      int           `env:"TEST_UNSET,asdf=asdf,default=1234"`
-	DefaultSliceInt []int         `env:"TEST_UNSET,asdf=asdf,default=1;2;3"`
+	DefaultSliceInt []int         `env:"TEST_UNSET,asdf=asdf,default=1"`
 	DefaultDuration time.Duration `env:"TEST_UNSET,asdf=asdf,default=24h"`
 	DefaultURL      *url.URL      `env:"TEST_UNSET,default=http://example.com"`
 }
@@ -137,12 +137,12 @@ func TestDecode(t *testing.T) {
 	os.Setenv("TEST_DURATION", "10m")
 	os.Setenv("TEST_URL", "https://example.com")
 	os.Setenv("TEST_INVALID_INT64", "asdf")
-	os.Setenv("TEST_STRING_SLICE", "foo;bar")
-	os.Setenv("TEST_INT64_SLICE", int64AsString+";"+int64AsString)
-	os.Setenv("TEST_UINT16_SLICE", "60000;50000")
-	os.Setenv("TEST_FLOAT64_SLICE", piAsString+";"+piAsString)
-	os.Setenv("TEST_BOOL_SLICE", "true; false; true")
-	os.Setenv("TEST_DURATION_SLICE", "10m; 20m")
+	os.Setenv("TEST_STRING_SLICE", "foo,bar")
+	os.Setenv("TEST_INT64_SLICE", int64AsString+","+int64AsString)
+	os.Setenv("TEST_UINT16_SLICE", "60000,50000")
+	os.Setenv("TEST_FLOAT64_SLICE", piAsString+","+piAsString)
+	os.Setenv("TEST_BOOL_SLICE", "true, false, true")
+	os.Setenv("TEST_DURATION_SLICE", "10m, 20m")
 	os.Setenv("TEST_URL_SLICE", "https://example.com")
 	os.Setenv("TEST_DECODER_STRUCT", "{\"string\":\"foo\"}")
 	os.Setenv("TEST_DECODER_STRUCT_PTR", "{\"string\":\"foo\"}")
@@ -274,7 +274,7 @@ func TestDecode(t *testing.T) {
 		t.Fatalf("Expected 1234, got %d", tc.DefaultInt)
 	}
 
-	expectedDefaultSlice := []int{1, 2, 3}
+	expectedDefaultSlice := []int{1}
 	if !reflect.DeepEqual(tc.DefaultSliceInt, expectedDefaultSlice) {
 		t.Fatalf("Expected %d, got %d", expectedDefaultSlice, tc.DefaultSliceInt)
 	}
@@ -517,7 +517,7 @@ type testConfigExport struct {
 	DefaultDuration time.Duration `env:"TEST_DEFAULT_DURATION,default=24h"`
 	DefaultURL      *url.URL      `env:"TEST_DEFAULT_URL,default=http://example.com"`
 	DefaultIntSet   int           `env:"TEST_DEFAULT_INT_SET,default=99"`
-	DefaultIntSlice []int         `env:"TEST_DEFAULT_INT_SLICE,default=99;33"`
+	DefaultIntSlice []int         `env:"TEST_DEFAULT_INT_SLICE,default=99"`
 }
 
 type nestedConfigExport struct {
@@ -603,13 +603,13 @@ func TestExport(t *testing.T) {
 	os.Setenv("TEST_BOOL", "true")
 	os.Setenv("TEST_DURATION", "10m")
 	os.Setenv("TEST_URL", "https://example.com")
-	os.Setenv("TEST_STRING_SLICE", "foo;bar")
+	os.Setenv("TEST_STRING_SLICE", "foo,bar")
 	os.Setenv("TEST_NESTED_STRING", "nest_foo")
 	os.Setenv("TEST_NESTED_STRING_POINTER", "nest_foo_ptr")
 	os.Setenv("TEST_NESTED_TWICE_STRING", "nest_twice_foo")
 	os.Setenv("TEST_REQUIRED_INT", "101")
 	os.Setenv("TEST_DEFAULT_INT_SET", "102")
-	os.Setenv("TEST_DEFAULT_INT_SLICE", "1;2;3")
+	os.Setenv("TEST_DEFAULT_INT_SLICE", "1,2,3")
 
 	var tc testConfigExport
 	tc.NestedPtr = &nestedConfigExportPointer{}
@@ -769,7 +769,7 @@ func TestExport(t *testing.T) {
 			Field:        "DefaultIntSlice",
 			EnvVar:       "TEST_DEFAULT_INT_SLICE",
 			Value:        "[1 2 3]",
-			DefaultValue: "99;33",
+			DefaultValue: "99",
 			HasDefault:   true,
 			UsesEnv:      true,
 		},