Skip to content

Commit 8d72aa7

Browse files
imkiraajithcnambiarWojtek Koronski
committed
feat: multinamespace name config
Support more flexible namespace naming format in the multi name space mode via configuration. Co-authored-by: Ajith Chandran <[email protected]> Co-authored-by: Wojtek Koronski <[email protected]>
1 parent 53e3b7d commit 8d72aa7

File tree

11 files changed

+331
-14
lines changed

11 files changed

+331
-14
lines changed

chart/values.schema.json

+26
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,10 @@
13341334
"type": "boolean",
13351335
"description": "Enabled specifies if multi namespace mode should get enabled"
13361336
},
1337+
"namespaceNameFormat": {
1338+
"$ref": "#/$defs/ExperimentalMultiNamespaceNameFormat",
1339+
"description": "NamespaceNameFormat allows you to customize the name of the physical namespaces."
1340+
},
13371341
"namespaceLabels": {
13381342
"additionalProperties": {
13391343
"type": "string"
@@ -1345,6 +1349,28 @@
13451349
"additionalProperties": false,
13461350
"type": "object"
13471351
},
1352+
"ExperimentalMultiNamespaceNameFormat": {
1353+
"properties": {
1354+
"prefix": {
1355+
"type": "string",
1356+
"description": "Prefix is the prefix added to the physical namespaces.\nIf empty, the default is to use \"vcluster\""
1357+
},
1358+
"rawBase": {
1359+
"type": "boolean",
1360+
"description": "If RawBase is true, use the virtual namespace as is, otherwise hash it."
1361+
},
1362+
"rawSuffix": {
1363+
"type": "boolean",
1364+
"description": "If RawSuffix is true, use the cluster name as is, otherwise hash it."
1365+
},
1366+
"avoidRedundantFormatting": {
1367+
"type": "boolean",
1368+
"description": "If AvoidRedundantFormatting is true, we check if base (the name between prefix and suffix)\nalready contains the prefix and suffix. In that case, we just return base, instead of\nformatting again. Otherwise, we always add prefix and suffix."
1369+
}
1370+
},
1371+
"additionalProperties": false,
1372+
"type": "object"
1373+
},
13481374
"ExperimentalSyncSettings": {
13491375
"properties": {
13501376
"disableSync": {

chart/values.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,8 @@ experimental:
862862
multiNamespaceMode:
863863
# Enabled specifies if multi namespace mode should get enabled
864864
enabled: false
865+
# NamespaceNameFormat allows you to customize the name of the physical namespaces.
866+
namespaceNameFormat: {}
865867

866868
# SyncSettings are advanced settings for the syncer controller.
867869
syncSettings:

config/config.go

+20
Original file line numberDiff line numberDiff line change
@@ -1632,10 +1632,30 @@ type ExperimentalMultiNamespaceMode struct {
16321632
// Enabled specifies if multi namespace mode should get enabled
16331633
Enabled bool `json:"enabled,omitempty"`
16341634

1635+
// NamespaceNameFormat allows you to customize the name of the physical namespaces.
1636+
NamespaceNameFormat ExperimentalMultiNamespaceNameFormat `json:"namespaceNameFormat,omitempty"`
1637+
16351638
// NamespaceLabels are extra labels that will be added by vCluster to each created namespace.
16361639
NamespaceLabels map[string]string `json:"namespaceLabels,omitempty"`
16371640
}
16381641

1642+
type ExperimentalMultiNamespaceNameFormat struct {
1643+
// Prefix is the prefix added to the physical namespaces.
1644+
// If empty, the default is to use "vcluster"
1645+
Prefix string `json:"prefix,omitempty"`
1646+
1647+
// If RawBase is true, use the virtual namespace as is, otherwise hash it.
1648+
RawBase bool `json:"rawBase,omitempty"`
1649+
1650+
// If RawSuffix is true, use the cluster name as is, otherwise hash it.
1651+
RawSuffix bool `json:"rawSuffix,omitempty"`
1652+
1653+
// If AvoidRedundantFormatting is true, we check if base (the name between prefix and suffix)
1654+
// already contains the prefix and suffix. In that case, we just return base, instead of
1655+
// formatting again. Otherwise, we always add prefix and suffix.
1656+
AvoidRedundantFormatting bool `json:"avoidRedundantFormatting,omitempty"`
1657+
}
1658+
16391659
type ExperimentalIsolatedControlPlane struct {
16401660
// Enabled specifies if the isolated control plane feature should be enabled.
16411661
Enabled bool `json:"enabled,omitempty" product:"pro"`

config/config_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,23 @@ func TestConfig_IsProFeatureEnabled(t *testing.T) {
334334
},
335335
expected: true,
336336
},
337+
{
338+
name: "Namespace reformatting on multinamespace mode does not use Pro features",
339+
config: &Config{
340+
Experimental: Experimental{
341+
MultiNamespaceMode: ExperimentalMultiNamespaceMode{
342+
Enabled: true,
343+
NamespaceNameFormat: ExperimentalMultiNamespaceNameFormat{
344+
Prefix: "foo",
345+
RawBase: true,
346+
RawSuffix: true,
347+
AvoidRedundantFormatting: true,
348+
},
349+
},
350+
},
351+
},
352+
expected: false,
353+
},
337354
}
338355

339356
for _, tt := range tests {

config/values.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ plugins: {}
512512
experimental:
513513
multiNamespaceMode:
514514
enabled: false
515+
namespaceNameFormat: {}
515516

516517
syncSettings:
517518
disableSync: false

pkg/config/validation.go

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ func ValidateConfigAndSetDefaults(config *VirtualClusterConfig) error {
100100
}
101101
}
102102

103+
// set multi namespace mode name format
104+
if config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix == "" {
105+
config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix = "vcluster"
106+
}
107+
103108
// check resolve dns
104109
err = validateMappings(config.Networking.ResolveDNS)
105110
if err != nil {

pkg/config/validation_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/loft-sh/vcluster/config"
@@ -125,3 +126,58 @@ func mutHook(clientCfg config.ValidatingWebhookClientConfig) config.MutatingWebh
125126
}
126127
return hook
127128
}
129+
130+
func TestValidateConfigAndSetDefaults(t *testing.T) {
131+
testCases := []struct {
132+
name string
133+
wantErr string
134+
config VirtualClusterConfig
135+
checkFunc func(config *VirtualClusterConfig) error
136+
}{
137+
{
138+
name: "multi-namespace namespace name formatting default prefix",
139+
wantErr: "",
140+
config: VirtualClusterConfig{},
141+
checkFunc: func(config *VirtualClusterConfig) error {
142+
if prefix := config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix; prefix != "vcluster" {
143+
return fmt.Errorf("unexpected prefix %q", prefix)
144+
}
145+
return nil
146+
},
147+
},
148+
{
149+
name: "multi-namespace namespace name formatting custom prefix",
150+
wantErr: "",
151+
config: VirtualClusterConfig{
152+
Config: config.Config{
153+
Experimental: config.Experimental{
154+
MultiNamespaceMode: config.ExperimentalMultiNamespaceMode{
155+
NamespaceNameFormat: config.ExperimentalMultiNamespaceNameFormat{
156+
Prefix: "foo",
157+
},
158+
},
159+
},
160+
},
161+
},
162+
checkFunc: func(config *VirtualClusterConfig) error {
163+
if prefix := config.Experimental.MultiNamespaceMode.NamespaceNameFormat.Prefix; prefix != "foo" {
164+
return fmt.Errorf("unexpected prefix %q", prefix)
165+
}
166+
return nil
167+
},
168+
},
169+
}
170+
for _, tt := range testCases {
171+
t.Run(tt.name, func(t *testing.T) {
172+
outputConfig := tt.config
173+
err := ValidateConfigAndSetDefaults(&outputConfig)
174+
if err != nil && (tt.wantErr == "" || tt.wantErr != err.Error()) {
175+
t.Errorf("wanted err to be %s but got %s", tt.wantErr, err.Error())
176+
} else if err == nil && tt.wantErr != "" {
177+
t.Errorf("wanted err to be %s but got nil", tt.wantErr)
178+
} else if err := tt.checkFunc(&outputConfig); err != nil {
179+
t.Errorf("wanted check err to be nil but got %s", err.Error())
180+
}
181+
})
182+
}
183+
}

pkg/setup/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func InitAndValidateConfig(ctx context.Context, vConfig *config.VirtualClusterCo
4747

4848
// get workload target namespace
4949
if vConfig.Experimental.MultiNamespaceMode.Enabled {
50-
translate.Default = translate.NewMultiNamespaceTranslator(vConfig.WorkloadNamespace)
50+
translate.Default = translate.NewMultiNamespaceTranslator(vConfig.WorkloadNamespace, vConfig.Experimental.MultiNamespaceMode.NamespaceNameFormat)
5151
} else {
5252
// ensure target namespace
5353
vConfig.WorkloadTargetNamespace = vConfig.Experimental.SyncSettings.TargetNamespace

pkg/util/translate/multi_namespace.go

+24-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"regexp"
88
"strings"
99

10+
"github.com/loft-sh/vcluster/config"
1011
corev1 "k8s.io/api/core/v1"
1112
"k8s.io/apimachinery/pkg/api/equality"
1213
"k8s.io/apimachinery/pkg/api/meta"
@@ -17,14 +18,16 @@ import (
1718

1819
var _ Translator = &multiNamespace{}
1920

20-
func NewMultiNamespaceTranslator(currentNamespace string) Translator {
21+
func NewMultiNamespaceTranslator(currentNamespace string, nameFormat config.ExperimentalMultiNamespaceNameFormat) Translator {
2122
return &multiNamespace{
2223
currentNamespace: currentNamespace,
24+
nameFormat: nameFormat,
2325
}
2426
}
2527

2628
type multiNamespace struct {
2729
currentNamespace string
30+
nameFormat config.ExperimentalMultiNamespaceNameFormat
2831
}
2932

3033
func (s *multiNamespace) SingleNamespaceTarget() bool {
@@ -95,7 +98,7 @@ func (s *multiNamespace) IsManagedCluster(obj runtime.Object) bool {
9598
}
9699

97100
func (s *multiNamespace) IsTargetedNamespace(ns string) bool {
98-
return strings.HasPrefix(ns, s.getNamespacePrefix()) && strings.HasSuffix(ns, getNamespaceSuffix(s.currentNamespace, VClusterName))
101+
return strings.HasPrefix(ns, s.getNamespacePrefix()) && strings.HasSuffix(ns, s.getNamespaceSuffix())
99102
}
100103

101104
func (s *multiNamespace) convertLabelKey(key string) string {
@@ -104,21 +107,30 @@ func (s *multiNamespace) convertLabelKey(key string) string {
104107
}
105108

106109
func (s *multiNamespace) getNamespacePrefix() string {
107-
return "vcluster"
110+
return s.nameFormat.Prefix
108111
}
109112

110113
func (s *multiNamespace) PhysicalNamespace(vNamespace string) string {
111-
return PhysicalNamespace(s.currentNamespace, vNamespace, s.getNamespacePrefix(), VClusterName)
112-
}
113-
114-
func PhysicalNamespace(currentNamespace, vNamespace, prefix, suffix string) string {
115-
sha := sha256.Sum256([]byte(vNamespace))
116-
return fmt.Sprintf("%s-%s-%s", prefix, hex.EncodeToString(sha[0:])[0:8], getNamespaceSuffix(currentNamespace, suffix))
114+
base := vNamespace
115+
if !s.nameFormat.RawBase {
116+
sha := sha256.Sum256([]byte(base))
117+
base = hex.EncodeToString(sha[0:])[0:8]
118+
}
119+
if s.nameFormat.AvoidRedundantFormatting && s.IsTargetedNamespace(base) {
120+
return base
121+
}
122+
prefix := s.getNamespacePrefix()
123+
suffix := s.getNamespaceSuffix()
124+
return fmt.Sprintf("%s-%s-%s", prefix, base, suffix)
117125
}
118126

119-
func getNamespaceSuffix(currentNamespace, suffix string) string {
120-
sha := sha256.Sum256([]byte(currentNamespace + "x" + suffix))
121-
return hex.EncodeToString(sha[0:])[0:8]
127+
func (s *multiNamespace) getNamespaceSuffix() string {
128+
suffix := VClusterName
129+
if !s.nameFormat.RawSuffix {
130+
sha := sha256.Sum256([]byte(s.currentNamespace + "x" + suffix))
131+
suffix = hex.EncodeToString(sha[0:])[0:8]
132+
}
133+
return suffix
122134
}
123135

124136
func (s *multiNamespace) TranslateLabelsCluster(vObj client.Object, pObj client.Object, syncedLabels []string) map[string]string {

0 commit comments

Comments
 (0)