Skip to content
20 changes: 15 additions & 5 deletions cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,18 +525,28 @@ type AuthServerStorageConfig struct {
}

// RedisStorageConfig configures Redis connection for auth server storage.
// Exactly one of addr (standalone) or sentinelConfig (Sentinel) must be set.
// Exactly one of addr or sentinelConfig must be set. Set clusterMode to true when
// addr points to a Redis Cluster discovery endpoint (GCP Memorystore Cluster,
// AWS ElastiCache cluster mode enabled).
//
// +kubebuilder:validation:XValidation:rule="(self.addr.size() > 0) != has(self.sentinelConfig)",message="exactly one of addr (standalone) or sentinelConfig (Sentinel) must be set"
// +kubebuilder:validation:XValidation:rule="(self.addr.size() > 0) != has(self.sentinelConfig)",message="exactly one of addr or sentinelConfig must be set"
// +kubebuilder:validation:XValidation:rule="!self.clusterMode || self.addr.size() > 0",message="clusterMode requires addr to be set"
//
//nolint:lll // CEL validation rules exceed line length limit
type RedisStorageConfig struct {
// Addr is the Redis server address for standalone mode (e.g., "host:port").
// Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
// a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
// Addr is the Redis server address (host:port). Required for standalone and cluster modes.
// Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
// AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
// Mutually exclusive with sentinelConfig.
// +optional
Addr string `json:"addr,omitempty"`

// ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
// Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
// cluster mode enabled). Requires addr to be set.
// +optional
ClusterMode bool `json:"clusterMode,omitempty"`

// SentinelConfig holds Redis Sentinel configuration.
// Use for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.
// +optional
Expand Down
10 changes: 5 additions & 5 deletions cmd/thv-operator/pkg/controllerutil/authserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,12 +544,11 @@ func buildStorageRunConfig(
return nil, fmt.Errorf("redis config is required when storage type is redis")
}

if redisConfig.Addr == "" && redisConfig.SentinelConfig == nil {
return nil, fmt.Errorf("either addr (standalone) or sentinel config is required for Redis storage")
}

if redisConfig.Addr != "" && redisConfig.SentinelConfig != nil {
return nil, fmt.Errorf("addr and sentinel config are mutually exclusive for Redis storage")
return nil, fmt.Errorf("addr and sentinelConfig are mutually exclusive for Redis storage")
}
if redisConfig.Addr == "" && redisConfig.SentinelConfig == nil {
return nil, fmt.Errorf("one of addr (standalone or cluster) or sentinelConfig (Sentinel) is required for Redis storage")
}

if redisConfig.ACLUserConfig == nil ||
Expand All @@ -569,6 +568,7 @@ func buildStorageRunConfig(

rc := &storage.RedisRunConfig{
Addr: redisConfig.Addr,
ClusterMode: redisConfig.ClusterMode,
AuthType: storage.AuthTypeACLUser,
ACLUserConfig: aclRunConfig,
KeyPrefix: keyPrefix,
Expand Down
36 changes: 33 additions & 3 deletions cmd/thv-operator/pkg/controllerutil/authserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1674,7 +1674,7 @@ func TestBuildStorageRunConfig(t *testing.T) {
errContains: "redis config is required",
},
{
name: "Redis storage missing both addr and sentinelConfig returns error",
name: "Redis storage missing addr and sentinelConfig returns error",
authConfig: &mcpv1beta1.EmbeddedAuthServerConfig{
Issuer: "https://auth.example.com",
Storage: &mcpv1beta1.AuthServerStorageConfig{
Expand All @@ -1688,7 +1688,7 @@ func TestBuildStorageRunConfig(t *testing.T) {
},
},
wantErr: true,
errContains: "either addr (standalone) or sentinel config is required",
errContains: "one of addr (standalone or cluster) or sentinelConfig (Sentinel) is required",
},
{
name: "Redis storage with both addr and sentinelConfig returns error",
Expand All @@ -1710,7 +1710,37 @@ func TestBuildStorageRunConfig(t *testing.T) {
},
},
wantErr: true,
errContains: "addr and sentinel config are mutually exclusive",
errContains: "mutually exclusive",
},
{
name: "Redis cluster mode builds correctly",
authConfig: &mcpv1beta1.EmbeddedAuthServerConfig{
Issuer: "https://auth.example.com",
Storage: &mcpv1beta1.AuthServerStorageConfig{
Type: mcpv1beta1.AuthServerStorageTypeRedis,
Redis: &mcpv1beta1.RedisStorageConfig{
Addr: "discovery.example.com:6379",
ClusterMode: true,
ACLUserConfig: &mcpv1beta1.RedisACLUserConfig{
UsernameSecretRef: &mcpv1beta1.SecretKeyRef{Name: "redis-secret", Key: "username"},
PasswordSecretRef: &mcpv1beta1.SecretKeyRef{Name: "redis-secret", Key: "password"},
},
},
},
},
checkFunc: func(t *testing.T, cfg *storage.RunConfig) {
t.Helper()
assert.Equal(t, string(storage.TypeRedis), cfg.Type)
require.NotNil(t, cfg.RedisConfig)
assert.Equal(t, "discovery.example.com:6379", cfg.RedisConfig.Addr)
assert.True(t, cfg.RedisConfig.ClusterMode)
assert.Nil(t, cfg.RedisConfig.SentinelConfig)
assert.Equal(t, storage.AuthTypeACLUser, cfg.RedisConfig.AuthType)
require.NotNil(t, cfg.RedisConfig.ACLUserConfig)
assert.Equal(t, authrunner.RedisUsernameEnvVar, cfg.RedisConfig.ACLUserConfig.UsernameEnvVar)
assert.Equal(t, authrunner.RedisPasswordEnvVar, cfg.RedisConfig.ACLUserConfig.PasswordEnvVar)
assert.Equal(t, "thv:auth:{default:test-server}:", cfg.RedisConfig.KeyPrefix)
},
},
{
name: "Redis storage with standalone addr builds correctly",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,17 @@ spec:
type: object
addr:
description: |-
Addr is the Redis server address for standalone mode (e.g., "host:port").
Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
Expand Down Expand Up @@ -442,9 +449,10 @@ spec:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr (standalone) or sentinelConfig
(Sentinel) must be set
- message: exactly one of addr or sentinelConfig must be set
rule: (self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!self.clusterMode || self.addr.size() > 0'
type:
default: memory
description: |-
Expand Down Expand Up @@ -1364,10 +1372,17 @@ spec:
type: object
addr:
description: |-
Addr is the Redis server address for standalone mode (e.g., "host:port").
Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
Expand Down Expand Up @@ -1490,9 +1505,10 @@ spec:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr (standalone) or sentinelConfig
(Sentinel) must be set
- message: exactly one of addr or sentinelConfig must be set
rule: (self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!self.clusterMode || self.addr.size() > 0'
type:
default: memory
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,17 @@ spec:
type: object
addr:
description: |-
Addr is the Redis server address for standalone mode (e.g., "host:port").
Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
Expand Down Expand Up @@ -315,9 +322,10 @@ spec:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr (standalone) or sentinelConfig
(Sentinel) must be set
- message: exactly one of addr or sentinelConfig must be set
rule: (self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!self.clusterMode || self.addr.size() > 0'
type:
default: memory
description: |-
Expand Down Expand Up @@ -2685,10 +2693,17 @@ spec:
type: object
addr:
description: |-
Addr is the Redis server address for standalone mode (e.g., "host:port").
Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
Expand Down Expand Up @@ -2811,9 +2826,10 @@ spec:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr (standalone) or sentinelConfig
(Sentinel) must be set
- message: exactly one of addr or sentinelConfig must be set
rule: (self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!self.clusterMode || self.addr.size() > 0'
type:
default: memory
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,17 @@ spec:
type: object
addr:
description: |-
Addr is the Redis server address for standalone mode (e.g., "host:port").
Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
Expand Down Expand Up @@ -445,9 +452,10 @@ spec:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr (standalone) or sentinelConfig
(Sentinel) must be set
- message: exactly one of addr or sentinelConfig must be set
rule: (self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!self.clusterMode || self.addr.size() > 0'
type:
default: memory
description: |-
Expand Down Expand Up @@ -1367,10 +1375,17 @@ spec:
type: object
addr:
description: |-
Addr is the Redis server address for standalone mode (e.g., "host:port").
Use for managed Redis services (GCP Memorystore, AWS ElastiCache) that present
a single endpoint and manage HA internally. Mutually exclusive with sentinelConfig.
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
Expand Down Expand Up @@ -1493,9 +1508,10 @@ spec:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr (standalone) or sentinelConfig
(Sentinel) must be set
- message: exactly one of addr or sentinelConfig must be set
rule: (self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!self.clusterMode || self.addr.size() > 0'
type:
default: memory
description: |-
Expand Down
Loading
Loading