Skip to content

Commit 029db8c

Browse files
authored
feat(core): add service negation for op mode (#2680)
### Proposed Changes * Add support for service negation when defining a operational mode ### Checklist - [ ] I have added or updated unit tests - [ ] I have added or updated integration tests (if appropriate) - [ ] I have added or updated documentation ### Testing Instructions
1 parent d5eff9f commit 029db8c

File tree

7 files changed

+641
-252
lines changed

7 files changed

+641
-252
lines changed

docs/Configuring.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The platform leverages [viper](https://github.com/spf13/viper) to help load conf
66

77
- [Platform Configuration](#platform-configuration)
88
- [Deployment Mode](#deployment-mode)
9+
- [Service Negation](#service-negation)
910
- [SDK Configuration](#sdk-configuration)
1011
- [Logger Configuration](#logger-configuration)
1112
- [Server Configuration](#server-configuration)
@@ -31,11 +32,29 @@ The platform is designed as a modular monolith, meaning that all services are bu
3132
- core: Runs essential services, including policy, authorization, and wellknown services.
3233
- kas: Runs the Key Access Server (KAS) service.
3334

35+
### Service Negation
3436

37+
You can exclude specific services from any mode using the negation syntax `-servicename`:
38+
39+
- **Syntax**: `mode: <base-mode>,-<service1>,-<service2>`
40+
- **Constraint**: At least one positive mode must be specified (negation-only modes like `-kas` will result in an error)
41+
- **Available services**: `policy`, `authorization`, `kas`, `entityresolution`, `wellknown`
42+
43+
**Examples:**
44+
```yaml
45+
# Run all services except Entity Resolution Service
46+
mode: all,-entityresolution
47+
48+
# Run core services except Policy Service
49+
mode: core,-policy
50+
51+
# Run all services except both KAS and Entity Resolution
52+
mode: all,-kas,-entityresolution
53+
```
3554
3655
| Field | Description | Default | Environment Variable |
3756
| ------ | ----------------------------------------------------------------------------- | ------- | -------------------- |
38-
| `mode` | Drives which services to run. Following modes are supported. (all, core, kas) | `all` | OPENTDF_MODE |
57+
| `mode` | Drives which services to run. Supported modes: `all`, `core`, `kas`. Use `-servicename` to exclude specific services (e.g., `all,-entityresolution`) | `all` | OPENTDF_MODE |
3958

4059
## SDK Configuration
4160

service/pkg/server/services.go

Lines changed: 62 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"embed"
66
"fmt"
77
"log/slog"
8-
"slices"
9-
"strings"
108

119
"github.com/go-viper/mapstructure/v2"
1210
"github.com/opentdf/platform/sdk"
@@ -30,92 +28,77 @@ import (
3028
"go.opentelemetry.io/otel/trace"
3129
)
3230

33-
const (
34-
modeALL = "all"
35-
modeCore = "core"
36-
modeKAS = "kas"
37-
modeERS = "entityresolution"
38-
modeEssential = "essential"
39-
40-
serviceKAS = "kas"
41-
servicePolicy = "policy"
42-
serviceWellKnown = "wellknown"
43-
serviceEntityResolution = "entityresolution"
44-
serviceAuthorization = "authorization"
31+
var (
32+
ServiceHealth ServiceName = "health"
33+
ServiceKAS ServiceName = "kas"
34+
ServicePolicy ServiceName = "policy"
35+
ServiceWellKnown ServiceName = "wellknown"
36+
ServiceEntityResolution ServiceName = "entityresolution"
37+
ServiceAuthorization ServiceName = "authorization"
4538
)
4639

47-
// registerEssentialServices registers the essential services to the given service registry.
48-
// It takes a serviceregistry.Registry as input and returns an error if registration fails.
49-
func registerEssentialServices(reg *serviceregistry.Registry) error {
40+
// getServiceConfigurations returns fresh service configurations each time it's called.
41+
// This prevents state sharing between test runs by creating new service instances.
42+
func getServiceConfigurations() []serviceregistry.ServiceConfiguration {
43+
return []serviceregistry.ServiceConfiguration{
44+
// Note: Health service is registered separately via RegisterEssentialServices
45+
{
46+
Name: ServicePolicy,
47+
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeCore},
48+
Services: policy.NewRegistrations(),
49+
},
50+
{
51+
Name: ServiceAuthorization,
52+
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeCore},
53+
Services: []serviceregistry.IService{authorization.NewRegistration(), authorizationV2.NewRegistration()},
54+
},
55+
{
56+
Name: ServiceKAS,
57+
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeKAS},
58+
Services: []serviceregistry.IService{kas.NewRegistration()},
59+
},
60+
{
61+
Name: ServiceWellKnown,
62+
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeCore},
63+
Services: []serviceregistry.IService{wellknown.NewRegistration()},
64+
},
65+
{
66+
Name: ServiceEntityResolution,
67+
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeERS},
68+
Services: []serviceregistry.IService{entityresolution.NewRegistration(), entityresolutionV2.NewRegistration()},
69+
},
70+
}
71+
}
72+
73+
// RegisterEssentialServices registers the essential services directly
74+
func RegisterEssentialServices(reg *serviceregistry.Registry) error {
5075
essentialServices := []serviceregistry.IService{
5176
health.NewRegistration(),
5277
}
53-
// Register the essential services
54-
for _, s := range essentialServices {
55-
if err := reg.RegisterService(s, modeEssential); err != nil {
56-
return err //nolint:wrapcheck // We are all friends here
78+
for _, svc := range essentialServices {
79+
if err := reg.RegisterService(svc, serviceregistry.ModeEssential); err != nil {
80+
return err
5781
}
5882
}
5983
return nil
6084
}
6185

62-
// registerCoreServices registers the core services based on the provided mode.
63-
// It returns the list of registered services and any error encountered during registration.
64-
func registerCoreServices(reg *serviceregistry.Registry, mode []string) ([]string, error) {
65-
var (
66-
services []serviceregistry.IService
67-
registeredServices []string
68-
)
69-
70-
for _, m := range mode {
71-
switch m {
72-
case "all":
73-
registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceKAS, serviceWellKnown, serviceEntityResolution}...)
74-
services = append(services, []serviceregistry.IService{
75-
authorization.NewRegistration(),
76-
authorizationV2.NewRegistration(),
77-
kas.NewRegistration(),
78-
wellknown.NewRegistration(),
79-
entityresolution.NewRegistration(),
80-
entityresolutionV2.NewRegistration(),
81-
}...)
82-
services = append(services, policy.NewRegistrations()...)
83-
case "core":
84-
registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceWellKnown}...)
85-
services = append(services, []serviceregistry.IService{
86-
authorization.NewRegistration(),
87-
authorizationV2.NewRegistration(),
88-
wellknown.NewRegistration(),
89-
}...)
90-
services = append(services, policy.NewRegistrations()...)
91-
case "kas":
92-
// If the mode is "kas", register only the KAS service
93-
registeredServices = append(registeredServices, serviceKAS)
94-
if err := reg.RegisterService(kas.NewRegistration(), modeKAS); err != nil {
95-
return nil, err //nolint:wrapcheck // We are all friends here
96-
}
97-
case "entityresolution":
98-
// If the mode is "entityresolution", register only the ERS service (v1 and v2)
99-
registeredServices = append(registeredServices, serviceEntityResolution)
100-
if err := reg.RegisterService(entityresolution.NewRegistration(), modeERS); err != nil {
101-
return nil, err //nolint:wrapcheck // We are all friends here
102-
}
103-
if err := reg.RegisterService(entityresolutionV2.NewRegistration(), modeERS); err != nil {
104-
return nil, err //nolint:wrapcheck // We are all friends here
105-
}
106-
default:
107-
continue
108-
}
86+
// RegisterCoreServices registers the core services using declarative configuration
87+
func RegisterCoreServices(reg *serviceregistry.Registry, modes []serviceregistry.ModeName) ([]string, error) {
88+
// Convert ModeName slice to string slice
89+
stringModes := make([]string, len(modes))
90+
for i, mode := range modes {
91+
stringModes[i] = mode.String()
10992
}
93+
return reg.RegisterServicesFromConfiguration(stringModes, getServiceConfigurations())
94+
}
11095

111-
// Register the services
112-
for _, s := range services {
113-
if err := reg.RegisterCoreService(s); err != nil {
114-
return nil, err //nolint:wrapcheck // We are all friends here
115-
}
116-
}
96+
// ServiceName represents a typed service identifier
97+
type ServiceName string
11798

118-
return registeredServices, nil
99+
// String returns the string representation of ServiceName
100+
func (s ServiceName) String() string {
101+
return string(s)
119102
}
120103

121104
type startServicesParams struct {
@@ -143,20 +126,10 @@ func startServices(ctx context.Context, params startServicesParams) (func(), err
143126
cacheManager := params.cacheManager
144127
keyManagerFactories := params.keyManagerFactories
145128

146-
for _, ns := range reg.GetNamespaces() {
147-
namespace, err := reg.GetNamespace(ns)
148-
if err != nil {
149-
// This is an internal inconsistency and should not happen.
150-
return nil, fmt.Errorf("namespace not found: %w", err)
151-
}
152-
// modeEnabled checks if the mode is enabled based on the configuration and namespace mode.
153-
// It returns true if the mode is "all" or "essential" in the configuration, or if it matches the namespace mode.
154-
modeEnabled := slices.ContainsFunc(cfg.Mode, func(m string) bool {
155-
if strings.EqualFold(m, modeALL) || strings.EqualFold(namespace.Mode, modeEssential) {
156-
return true
157-
}
158-
return strings.EqualFold(m, namespace.Mode)
159-
})
129+
// Iterate through the registered namespaces
130+
for ns, namespace := range reg.GetNamespaces() {
131+
// Check if this namespace should be enabled based on configured modes
132+
modeEnabled := namespace.IsEnabled(cfg.Mode)
160133

161134
// Skip the namespace if the mode is not enabled
162135
if !modeEnabled {

0 commit comments

Comments
 (0)